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