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