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