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