AutofillManagerServiceImpl.java revision 021b878a23d4a42eca927161dab0acbc6b0ece12
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.FillRequest.FLAG_MANUAL_REQUEST;
20import static android.view.autofill.AutofillManager.ACTION_START_SESSION;
21import static android.view.autofill.AutofillManager.NO_SESSION;
22
23import static com.android.server.autofill.Helper.sDebug;
24import static com.android.server.autofill.Helper.sVerbose;
25
26import android.annotation.NonNull;
27import android.annotation.Nullable;
28import android.app.ActivityManager;
29import android.app.AppGlobals;
30import android.app.IActivityManager;
31import android.content.ComponentName;
32import android.content.Context;
33import android.content.pm.ApplicationInfo;
34import android.content.pm.PackageManager;
35import android.content.pm.ServiceInfo;
36import android.graphics.Rect;
37import android.graphics.drawable.Drawable;
38import android.metrics.LogMaker;
39import android.os.AsyncTask;
40import android.os.Binder;
41import android.os.Bundle;
42import android.os.IBinder;
43import android.os.Looper;
44import android.os.RemoteCallbackList;
45import android.os.RemoteException;
46import android.os.UserManager;
47import android.provider.Settings;
48import android.service.autofill.AutofillService;
49import android.service.autofill.AutofillServiceInfo;
50import android.service.autofill.FillEventHistory;
51import android.service.autofill.FillEventHistory.Event;
52import android.service.autofill.FillResponse;
53import android.service.autofill.IAutoFillService;
54import android.text.TextUtils;
55import android.util.ArraySet;
56import android.util.DebugUtils;
57import android.util.LocalLog;
58import android.util.Slog;
59import android.util.SparseArray;
60import android.view.autofill.AutofillId;
61import android.view.autofill.AutofillManager;
62import android.view.autofill.AutofillValue;
63import android.view.autofill.IAutoFillManagerClient;
64
65import com.android.internal.R;
66import com.android.internal.annotations.GuardedBy;
67import com.android.internal.logging.MetricsLogger;
68import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
69import com.android.internal.os.HandlerCaller;
70import com.android.server.autofill.ui.AutoFillUI;
71
72import java.io.PrintWriter;
73import java.util.ArrayList;
74import java.util.Random;
75
76/**
77 * Bridge between the {@code system_server}'s {@link AutofillManagerService} and the
78 * app's {@link IAutoFillService} implementation.
79 *
80 */
81final class AutofillManagerServiceImpl {
82
83    private static final String TAG = "AutofillManagerServiceImpl";
84    private static final int MAX_SESSION_ID_CREATE_TRIES = 2048;
85
86    /** Minimum interval to prune abandoned sessions */
87    private static final int MAX_ABANDONED_SESSION_MILLIS = 30000;
88
89    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    private final MetricsLogger mMetricsLogger = new MetricsLogger();
96
97    private RemoteCallbackList<IAutoFillManagerClient> mClients;
98    private AutofillServiceInfo mInfo;
99
100    private static final Random sRandom = new Random();
101
102    private final LocalLog mRequestsHistory;
103    private final LocalLog mUiLatencyHistory;
104
105    /**
106     * Whether service was disabled for user due to {@link UserManager} restrictions.
107     */
108    private boolean mDisabled;
109
110    /**
111     * Caches whether the setup completed for the current user.
112     */
113    @GuardedBy("mLock")
114    private boolean mSetupComplete;
115
116    private final HandlerCaller.Callback mHandlerCallback = (msg) -> {
117        switch (msg.what) {
118            case MSG_SERVICE_SAVE:
119                handleSessionSave(msg.arg1);
120                break;
121            default:
122                Slog.w(TAG, "invalid msg on handler: " + msg);
123        }
124    };
125
126    private final HandlerCaller mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(),
127            mHandlerCallback, true);
128
129    /**
130     * Cache of pending {@link Session}s, keyed by sessionId.
131     *
132     * <p>They're kept until the {@link AutofillService} finished handling a request, an error
133     * occurs, or the session is abandoned.
134     */
135    @GuardedBy("mLock")
136    private final SparseArray<Session> mSessions = new SparseArray<>();
137
138    /** The last selection */
139    @GuardedBy("mLock")
140    private FillEventHistory mEventHistory;
141
142    /** When was {@link PruneTask} last executed? */
143    private long mLastPrune = 0;
144
145    AutofillManagerServiceImpl(Context context, Object lock, LocalLog requestsHistory,
146            LocalLog uiLatencyHistory, int userId, AutoFillUI ui, boolean disabled) {
147        mContext = context;
148        mLock = lock;
149        mRequestsHistory = requestsHistory;
150        mUiLatencyHistory = uiLatencyHistory;
151        mUserId = userId;
152        mUi = ui;
153        updateLocked(disabled);
154    }
155
156    @Nullable
157    CharSequence getServiceName() {
158        final String packageName = getServicePackageName();
159        if (packageName == null) {
160            return null;
161        }
162
163        try {
164            final PackageManager pm = mContext.getPackageManager();
165            final ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
166            return pm.getApplicationLabel(info);
167        } catch (Exception e) {
168            Slog.e(TAG, "Could not get label for " + packageName + ": " + e);
169            return packageName;
170        }
171    }
172
173    @Nullable
174    String getServicePackageName() {
175        final ComponentName serviceComponent = getServiceComponentName();
176        if (serviceComponent != null) {
177            return serviceComponent.getPackageName();
178        }
179        return null;
180    }
181
182    ComponentName getServiceComponentName() {
183        synchronized (mLock) {
184            if (mInfo == null) {
185                return null;
186            }
187            return mInfo.getServiceInfo().getComponentName();
188        }
189    }
190
191    private boolean isSetupCompletedLocked() {
192        final String setupComplete = Settings.Secure.getStringForUser(
193                mContext.getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, mUserId);
194        return "1".equals(setupComplete);
195    }
196
197    private String getComponentNameFromSettings() {
198        return Settings.Secure.getStringForUser(
199                mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, mUserId);
200    }
201
202    void updateLocked(boolean disabled) {
203        final boolean wasEnabled = isEnabled();
204        if (sVerbose) {
205            Slog.v(TAG, "updateLocked(u=" + mUserId + "): wasEnabled=" + wasEnabled
206                    + ", mSetupComplete= " + mSetupComplete
207                    + ", disabled=" + disabled + ", mDisabled=" + mDisabled);
208        }
209        mSetupComplete = isSetupCompletedLocked();
210        mDisabled = disabled;
211        ComponentName serviceComponent = null;
212        ServiceInfo serviceInfo = null;
213        final String componentName = getComponentNameFromSettings();
214        if (!TextUtils.isEmpty(componentName)) {
215            try {
216                serviceComponent = ComponentName.unflattenFromString(componentName);
217                serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
218                        0, mUserId);
219            } catch (RuntimeException | RemoteException e) {
220                Slog.e(TAG, "Bad autofill service name " + componentName + ": " + e);
221                return;
222            }
223        }
224        try {
225            if (serviceInfo != null) {
226                mInfo = new AutofillServiceInfo(mContext.getPackageManager(),
227                        serviceComponent, mUserId);
228                if (sDebug) Slog.d(TAG, "Set component for user " + mUserId + " as " + mInfo);
229            } else {
230                mInfo = null;
231                if (sDebug) Slog.d(TAG, "Reset component for user " + mUserId);
232            }
233            final boolean isEnabled = isEnabled();
234            if (wasEnabled != isEnabled) {
235                if (!isEnabled) {
236                    final int sessionCount = mSessions.size();
237                    for (int i = sessionCount - 1; i >= 0; i--) {
238                        final Session session = mSessions.valueAt(i);
239                        session.removeSelfLocked();
240                    }
241                }
242                sendStateToClients(false);
243            }
244        } catch (Exception e) {
245            Slog.e(TAG, "Bad AutofillService '" + componentName + "': " + e);
246        }
247    }
248
249    boolean addClientLocked(IAutoFillManagerClient client) {
250        if (mClients == null) {
251            mClients = new RemoteCallbackList<>();
252        }
253        mClients.register(client);
254        return isEnabled();
255    }
256
257    void setAuthenticationResultLocked(Bundle data, int sessionId, int authenticationId, int uid) {
258        if (!isEnabled()) {
259            return;
260        }
261        final Session session = mSessions.get(sessionId);
262        if (session != null && uid == session.uid) {
263            session.setAuthenticationResultLocked(data, authenticationId);
264        }
265    }
266
267    void setHasCallback(int sessionId, int uid, boolean hasIt) {
268        if (!isEnabled()) {
269            return;
270        }
271        final Session session = mSessions.get(sessionId);
272        if (session != null && uid == session.uid) {
273            synchronized (mLock) {
274                session.setHasCallbackLocked(hasIt);
275            }
276        }
277    }
278
279    int startSessionLocked(@NonNull IBinder activityToken, int uid,
280            @NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId,
281            @NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback,
282            int flags, @NonNull String packageName) {
283        if (!isEnabled()) {
284            return 0;
285        }
286        if (sVerbose) Slog.v(TAG, "startSession(): token=" + activityToken + ", flags=" + flags);
287
288        // Occasionally clean up abandoned sessions
289        pruneAbandonedSessionsLocked();
290
291        final Session newSession = createSessionByTokenLocked(activityToken, uid, appCallbackToken,
292                hasCallback, packageName);
293        if (newSession == null) {
294            return NO_SESSION;
295        }
296
297        final String historyItem =
298                "id=" + newSession.id + " uid=" + uid + " s=" + mInfo.getServiceInfo().packageName
299                        + " u=" + mUserId + " i=" + autofillId + " b=" + virtualBounds + " hc=" +
300                        hasCallback + " f=" + flags;
301        mRequestsHistory.log(historyItem);
302
303        newSession.updateLocked(autofillId, virtualBounds, value, ACTION_START_SESSION, flags);
304
305        return newSession.id;
306    }
307
308    /**
309     * Remove abandoned sessions if needed.
310     */
311    private void pruneAbandonedSessionsLocked() {
312        long now = System.currentTimeMillis();
313        if (mLastPrune < now - MAX_ABANDONED_SESSION_MILLIS) {
314            mLastPrune = now;
315
316            if (mSessions.size() > 0) {
317                (new PruneTask()).execute();
318            }
319        }
320    }
321
322    void finishSessionLocked(int sessionId, int uid) {
323        if (!isEnabled()) {
324            return;
325        }
326
327        final Session session = mSessions.get(sessionId);
328        if (session == null || uid != session.uid) {
329            if (sVerbose) {
330                Slog.v(TAG, "finishSessionLocked(): no session for " + sessionId + "(" + uid + ")");
331            }
332            return;
333        }
334
335        final boolean finished = session.showSaveLocked();
336        if (sVerbose) Slog.v(TAG, "finishSessionLocked(): session finished on save? " + finished);
337
338        if (finished) {
339            session.removeSelfLocked();
340        }
341    }
342
343    void cancelSessionLocked(int sessionId, int uid) {
344        if (!isEnabled()) {
345            return;
346        }
347
348        final Session session = mSessions.get(sessionId);
349        if (session == null || uid != session.uid) {
350            Slog.w(TAG, "cancelSessionLocked(): no session for " + sessionId + "(" + uid + ")");
351            return;
352        }
353        session.removeSelfLocked();
354    }
355
356    void disableOwnedAutofillServicesLocked(int uid) {
357        Slog.i(TAG, "disableOwnedServices(" + uid + "): " + mInfo);
358        if (mInfo == null) return;
359
360        final ServiceInfo serviceInfo = mInfo.getServiceInfo();
361        if (serviceInfo.applicationInfo.uid != uid) {
362            Slog.w(TAG, "disableOwnedServices(): ignored when called by UID " + uid
363                    + " instead of " + serviceInfo.applicationInfo.uid
364                    + " for service " + mInfo);
365            return;
366        }
367
368
369        final long identity = Binder.clearCallingIdentity();
370        try {
371            final String autoFillService = getComponentNameFromSettings();
372            final ComponentName componentName = serviceInfo.getComponentName();
373            if (componentName.equals(ComponentName.unflattenFromString(autoFillService))) {
374                mMetricsLogger.action(MetricsEvent.AUTOFILL_SERVICE_DISABLED_SELF,
375                        componentName.getPackageName());
376                Settings.Secure.putStringForUser(mContext.getContentResolver(),
377                        Settings.Secure.AUTOFILL_SERVICE, null, mUserId);
378                destroySessionsLocked();
379            } else {
380                Slog.w(TAG, "disableOwnedServices(): ignored because current service ("
381                        + serviceInfo + ") does not match Settings (" + autoFillService + ")");
382            }
383        } finally {
384            Binder.restoreCallingIdentity(identity);
385        }
386    }
387
388    private Session createSessionByTokenLocked(@NonNull IBinder activityToken, int uid,
389            @NonNull IBinder appCallbackToken, boolean hasCallback, @NonNull String packageName) {
390        // use random ids so that one app cannot know that another app creates sessions
391        int sessionId;
392        int tries = 0;
393        do {
394            tries++;
395            if (tries > MAX_SESSION_ID_CREATE_TRIES) {
396                Slog.w(TAG, "Cannot create session in " + MAX_SESSION_ID_CREATE_TRIES + " tries");
397                return null;
398            }
399
400            sessionId = sRandom.nextInt();
401        } while (sessionId == NO_SESSION || mSessions.indexOfKey(sessionId) >= 0);
402
403        final Session newSession = new Session(this, mUi, mContext, mHandlerCaller, mUserId, mLock,
404                sessionId, uid, activityToken, appCallbackToken, hasCallback,
405                mUiLatencyHistory, mInfo.getServiceInfo().getComponentName(), packageName);
406        mSessions.put(newSession.id, newSession);
407
408        return newSession;
409    }
410
411    /**
412     * Restores a session after an activity was temporarily destroyed.
413     *
414     * @param sessionId The id of the session to restore
415     * @param uid UID of the process that tries to restore the session
416     * @param activityToken The new instance of the activity
417     * @param appCallback The callbacks to the activity
418     */
419    boolean restoreSession(int sessionId, int uid, @NonNull IBinder activityToken,
420            @NonNull IBinder appCallback) {
421        final Session session = mSessions.get(sessionId);
422
423        if (session == null || uid != session.uid) {
424            return false;
425        } else {
426            session.switchActivity(activityToken, appCallback);
427            return true;
428        }
429    }
430
431    /**
432     * Updates a session and returns whether it should be restarted.
433     */
434    boolean updateSessionLocked(int sessionId, int uid, AutofillId autofillId, Rect virtualBounds,
435            AutofillValue value, int action, int flags) {
436        final Session session = mSessions.get(sessionId);
437        if (session == null || session.uid != uid) {
438            if ((flags & FLAG_MANUAL_REQUEST) != 0) {
439                if (sDebug) {
440                    Slog.d(TAG, "restarting session " + sessionId + " due to manual request on "
441                            + autofillId);
442                }
443                return true;
444            }
445            if (sVerbose) {
446                Slog.v(TAG, "updateSessionLocked(): session gone for " + sessionId
447                        + "(" + uid + ")");
448            }
449            return false;
450        }
451
452        session.updateLocked(autofillId, virtualBounds, value, action, flags);
453        return false;
454    }
455
456    void removeSessionLocked(int sessionId) {
457        mSessions.remove(sessionId);
458    }
459
460    private void handleSessionSave(int sessionId) {
461        synchronized (mLock) {
462            final Session session = mSessions.get(sessionId);
463            if (session == null) {
464                Slog.w(TAG, "handleSessionSave(): already gone: " + sessionId);
465
466                return;
467            }
468            session.callSaveLocked();
469        }
470    }
471
472    void onPendingSaveUi(int operation, @NonNull IBinder token) {
473        if (sVerbose) Slog.v(TAG, "onPendingSaveUi(" + operation + "): " + token);
474        synchronized (mLock) {
475            final int sessionCount = mSessions.size();
476            for (int i = sessionCount - 1; i >= 0; i--) {
477                final Session session = mSessions.valueAt(i);
478                if (session.isSaveUiPendingForTokenLocked(token)) {
479                    session.onPendingSaveUi(operation, token);
480                    return;
481                }
482            }
483        }
484        if (sDebug) {
485            Slog.d(TAG, "No pending Save UI for token " + token + " and operation "
486                    + DebugUtils.flagsToString(AutofillManager.class, "PENDING_UI_OPERATION_",
487                            operation));
488        }
489    }
490
491    void destroyLocked() {
492        if (sVerbose) Slog.v(TAG, "destroyLocked()");
493
494        final int numSessions = mSessions.size();
495        final ArraySet<RemoteFillService> remoteFillServices = new ArraySet<>(numSessions);
496        for (int i = 0; i < numSessions; i++) {
497            final RemoteFillService remoteFillService = mSessions.valueAt(i).destroyLocked();
498            if (remoteFillService != null) {
499                remoteFillServices.add(remoteFillService);
500            }
501        }
502        mSessions.clear();
503        for (int i = 0; i < remoteFillServices.size(); i++) {
504            remoteFillServices.valueAt(i).destroy();
505        }
506
507        sendStateToClients(true);
508    }
509
510    @NonNull
511    CharSequence getServiceLabel() {
512        return mInfo.getServiceInfo().loadLabel(mContext.getPackageManager());
513    }
514
515    @NonNull
516    Drawable getServiceIcon() {
517        return mInfo.getServiceInfo().loadIcon(mContext.getPackageManager());
518    }
519
520    /**
521     * Initializes the last fill selection after an autofill service returned a new
522     * {@link FillResponse}.
523     */
524    void setLastResponse(int serviceUid, int sessionId, @NonNull FillResponse response) {
525        synchronized (mLock) {
526            mEventHistory = new FillEventHistory(serviceUid, sessionId, response.getClientState());
527        }
528    }
529
530    /**
531     * Resets the last fill selection.
532     */
533    void resetLastResponse() {
534        synchronized (mLock) {
535            mEventHistory = null;
536        }
537    }
538
539    private boolean isValidEventLocked(String method, int sessionId) {
540        if (mEventHistory == null) {
541            Slog.w(TAG, method + ": not logging event because history is null");
542            return false;
543        }
544        if (sessionId != mEventHistory.getSessionId()) {
545            if (sDebug) {
546                Slog.d(TAG, method + ": not logging event for session " + sessionId
547                        + " because tracked session is " + mEventHistory.getSessionId());
548            }
549            return false;
550        }
551        return true;
552    }
553
554    /**
555     * Updates the last fill selection when an authentication was selected.
556     */
557    void setAuthenticationSelected(int sessionId) {
558        synchronized (mLock) {
559            if (isValidEventLocked("setAuthenticationSelected()", sessionId)) {
560                mEventHistory.addEvent(new Event(Event.TYPE_AUTHENTICATION_SELECTED, null));
561            }
562        }
563    }
564
565    /**
566     * Updates the last fill selection when an dataset authentication was selected.
567     */
568    void logDatasetAuthenticationSelected(@Nullable String selectedDataset, int sessionId) {
569        synchronized (mLock) {
570            if (isValidEventLocked("logDatasetAuthenticationSelected()", sessionId)) {
571                mEventHistory.addEvent(
572                        new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset));
573            }
574        }
575    }
576
577    /**
578     * Updates the last fill selection when an save Ui is shown.
579     */
580    void logSaveShown(int sessionId) {
581        synchronized (mLock) {
582            if (isValidEventLocked("logSaveShown()", sessionId)) {
583                mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null));
584            }
585        }
586    }
587
588    /**
589     * Updates the last fill response when a dataset was selected.
590     */
591    void logDatasetSelected(@Nullable String selectedDataset, int sessionId) {
592        synchronized (mLock) {
593            if (isValidEventLocked("setDatasetSelected()", sessionId)) {
594                mEventHistory.addEvent(new Event(Event.TYPE_DATASET_SELECTED, selectedDataset));
595            }
596        }
597    }
598
599    /**
600     * Gets the fill event history.
601     *
602     * @param callingUid The calling uid
603     *
604     * @return The history or {@code null} if there is none.
605     */
606    FillEventHistory getFillEventHistory(int callingUid) {
607        synchronized (mLock) {
608            if (mEventHistory != null && mEventHistory.getServiceUid() == callingUid) {
609                return mEventHistory;
610            }
611        }
612
613        return null;
614    }
615
616    void dumpLocked(String prefix, PrintWriter pw) {
617        final String prefix2 = prefix + "  ";
618
619        pw.print(prefix); pw.print("User: "); pw.println(mUserId);
620        pw.print(prefix); pw.print("Component: "); pw.println(mInfo != null
621                ? mInfo.getServiceInfo().getComponentName() : null);
622        pw.print(prefix); pw.print("Component from settings: ");
623            pw.println(getComponentNameFromSettings());
624        pw.print(prefix); pw.print("Default component: ");
625            pw.println(mContext.getString(R.string.config_defaultAutofillService));
626        pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled);
627        pw.print(prefix); pw.print("Setup complete: "); pw.println(mSetupComplete);
628        pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune);
629
630        final int size = mSessions.size();
631        if (size == 0) {
632            pw.print(prefix); pw.println("No sessions");
633        } else {
634            pw.print(prefix); pw.print(size); pw.println(" sessions:");
635            for (int i = 0; i < size; i++) {
636                pw.print(prefix); pw.print("#"); pw.println(i + 1);
637                mSessions.valueAt(i).dumpLocked(prefix2, pw);
638            }
639        }
640
641        if (mEventHistory == null || mEventHistory.getEvents() == null
642                || mEventHistory.getEvents().size() == 0) {
643            pw.print(prefix); pw.println("No event on last fill response");
644        } else {
645            pw.print(prefix); pw.println("Events of last fill response:");
646            pw.print(prefix);
647
648            int numEvents = mEventHistory.getEvents().size();
649            for (int i = 0; i < numEvents; i++) {
650                final Event event = mEventHistory.getEvents().get(i);
651                pw.println("  " + i + ": eventType=" + event.getType() + " datasetId="
652                        + event.getDatasetId());
653            }
654        }
655    }
656
657    void destroySessionsLocked() {
658        if (mSessions.size() == 0) {
659            mUi.destroyAll(null, null, false);
660            return;
661        }
662        while (mSessions.size() > 0) {
663            mSessions.valueAt(0).forceRemoveSelfLocked();
664        }
665    }
666
667    // TODO(b/64940307): remove this method if SaveUI is refactored to be attached on activities
668    void destroyFinishedSessionsLocked() {
669        final int sessionCount = mSessions.size();
670        for (int i = sessionCount - 1; i >= 0; i--) {
671            final Session session = mSessions.valueAt(i);
672            if (session.isSavingLocked()) {
673                if (sDebug) Slog.d(TAG, "destroyFinishedSessionsLocked(): " + session.id);
674                session.forceRemoveSelfLocked();
675            }
676        }
677    }
678
679    void listSessionsLocked(ArrayList<String> output) {
680        final int numSessions = mSessions.size();
681        for (int i = 0; i < numSessions; i++) {
682            output.add((mInfo != null ? mInfo.getServiceInfo().getComponentName()
683                    : null) + ":" + mSessions.keyAt(i));
684        }
685    }
686
687    private void sendStateToClients(boolean resetClient) {
688        final RemoteCallbackList<IAutoFillManagerClient> clients;
689        final int userClientCount;
690        synchronized (mLock) {
691            if (mClients == null) {
692                return;
693            }
694            clients = mClients;
695            userClientCount = clients.beginBroadcast();
696        }
697        try {
698            for (int i = 0; i < userClientCount; i++) {
699                final IAutoFillManagerClient client = clients.getBroadcastItem(i);
700                try {
701                    final boolean resetSession;
702                    synchronized (mLock) {
703                        resetSession = resetClient || isClientSessionDestroyedLocked(client);
704                    }
705                    client.setState(isEnabled(), resetSession, resetClient);
706                } catch (RemoteException re) {
707                    /* ignore */
708                }
709            }
710        } finally {
711            clients.finishBroadcast();
712        }
713    }
714
715    private boolean isClientSessionDestroyedLocked(IAutoFillManagerClient client) {
716        final int sessionCount = mSessions.size();
717        for (int i = 0; i < sessionCount; i++) {
718            final Session session = mSessions.valueAt(i);
719            if (session.getClient().equals(client)) {
720                return session.isDestroyed();
721            }
722        }
723        return true;
724    }
725
726    boolean isEnabled() {
727        return mSetupComplete && mInfo != null && !mDisabled;
728    }
729
730    @Override
731    public String toString() {
732        return "AutofillManagerServiceImpl: [userId=" + mUserId
733                + ", component=" + (mInfo != null
734                ? mInfo.getServiceInfo().getComponentName() : null) + "]";
735    }
736
737    /** Task used to prune abandoned session */
738    private class PruneTask extends AsyncTask<Void, Void, Void> {
739        @Override
740        protected Void doInBackground(Void... ignored) {
741            int numSessionsToRemove;
742
743            SparseArray<IBinder> sessionsToRemove;
744
745            synchronized (mLock) {
746                numSessionsToRemove = mSessions.size();
747                sessionsToRemove = new SparseArray<>(numSessionsToRemove);
748
749                for (int i = 0; i < numSessionsToRemove; i++) {
750                    Session session = mSessions.valueAt(i);
751
752                    sessionsToRemove.put(session.id, session.getActivityTokenLocked());
753                }
754            }
755
756            IActivityManager am = ActivityManager.getService();
757
758            // Only remove sessions which's activities are not known to the activity manager anymore
759            for (int i = 0; i < numSessionsToRemove; i++) {
760                try {
761                    // The activity manager cannot resolve activities that have been removed
762                    if (am.getActivityClassForToken(sessionsToRemove.valueAt(i)) != null) {
763                        sessionsToRemove.removeAt(i);
764                        i--;
765                        numSessionsToRemove--;
766                    }
767                } catch (RemoteException e) {
768                    Slog.w(TAG, "Cannot figure out if activity is finished", e);
769                }
770            }
771
772            synchronized (mLock) {
773                for (int i = 0; i < numSessionsToRemove; i++) {
774                    Session sessionToRemove = mSessions.get(sessionsToRemove.keyAt(i));
775
776                    if (sessionToRemove != null && sessionsToRemove.valueAt(i)
777                            == sessionToRemove.getActivityTokenLocked()) {
778                        if (sessionToRemove.isSavingLocked()) {
779                            if (sVerbose) {
780                                Slog.v(TAG, "Session " + sessionToRemove.id + " is saving");
781                            }
782                        } else {
783                            if (sDebug) {
784                                Slog.i(TAG, "Prune session " + sessionToRemove.id + " ("
785                                    + sessionToRemove.getActivityTokenLocked() + ")");
786                            }
787                            sessionToRemove.removeSelfLocked();
788                        }
789                    }
790                }
791            }
792
793            return null;
794        }
795    }
796}
797