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