OSDN Git Service

Prune abandoned autofill sessions
authorPhilip P. Moltmann <moltmann@google.com>
Fri, 5 May 2017 20:30:38 +0000 (13:30 -0700)
committerPhilip P. Moltmann <moltmann@google.com>
Fri, 5 May 2017 22:15:14 +0000 (15:15 -0700)
Regularly check if the sessions are still valid and clear the ones that
are not.

Test: 1. Started session in dialer
      2. Home button (dialer goes in background)
      3. kill -9 dialer
      4. Wait 30 seconds
      5. Start session is messenger
      6. dialer session stays

      1. Started session in dialer
      2. Home button (dialer goes in background)
      3. kill -9 dialer
      4. Swipe dialer from recents
      5. Wait 30 seconds
      5. Start session is messenger
      6. dialer session is removed
Fixes: 38005472

Change-Id: I8199ce44777b313141ee1eab6c8de5ad5089474a

services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
services/autofill/java/com/android/server/autofill/Session.java
services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java

index 4507eae..3ab8b8d 100644 (file)
@@ -19,17 +19,21 @@ package com.android.server.autofill;
 import static android.view.autofill.AutofillManager.ACTION_START_SESSION;
 import static android.view.autofill.AutofillManager.NO_SESSION;
 
+import static com.android.server.autofill.Helper.sDebug;
 import static com.android.server.autofill.Helper.sVerbose;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.app.AppGlobals;
+import android.app.IActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
 import android.graphics.Rect;
+import android.os.AsyncTask;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -46,6 +50,7 @@ import android.service.autofill.FillEventHistory.Event;
 import android.service.autofill.FillResponse;
 import android.service.autofill.IAutoFillService;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.LocalLog;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -72,6 +77,9 @@ final class AutofillManagerServiceImpl {
     private static final String TAG = "AutofillManagerServiceImpl";
     private static final int MAX_SESSION_ID_CREATE_TRIES = 2048;
 
+    /** Minimum interval to prune abandoned sessions */
+    private static final int MAX_ABANDONED_SESSION_MILLIS = 30000;
+
     static final int MSG_SERVICE_SAVE = 1;
 
     private final int mUserId;
@@ -104,10 +112,10 @@ final class AutofillManagerServiceImpl {
             mHandlerCallback, true);
 
     /**
-     * Cache of pending {@link Session}s, keyed by {@code activityToken}.
+     * Cache of pending {@link Session}s, keyed by sessionId.
      *
      * <p>They're kept until the {@link AutofillService} finished handling a request, an error
-     * occurs, or the session times out.
+     * occurs, or the session is abandoned.
      */
     @GuardedBy("mLock")
     private final SparseArray<Session> mSessions = new SparseArray<>();
@@ -116,6 +124,9 @@ final class AutofillManagerServiceImpl {
     @GuardedBy("mLock")
     private FillEventHistory mEventHistory;
 
+    /** When was {@link PruneTask} last executed? */
+    private long mLastPrune = 0;
+
     AutofillManagerServiceImpl(Context context, Object lock, LocalLog requestsHistory,
             int userId, AutoFillUI ui, boolean disabled) {
         mContext = context;
@@ -260,6 +271,9 @@ final class AutofillManagerServiceImpl {
             return 0;
         }
 
+        // Occasionally clean up abandoned sessions
+        pruneAbandonedSessionsLocked();
+
         final Session newSession = createSessionByTokenLocked(activityToken, uid, windowToken,
                 appCallbackToken, hasCallback, flags, packageName);
         if (newSession == null) {
@@ -277,6 +291,20 @@ final class AutofillManagerServiceImpl {
         return newSession.id;
     }
 
+    /**
+     * Remove abandoned sessions if needed.
+     */
+    private void pruneAbandonedSessionsLocked() {
+        long now = System.currentTimeMillis();
+        if (mLastPrune < now - MAX_ABANDONED_SESSION_MILLIS) {
+            mLastPrune = now;
+
+            if (mSessions.size() > 0) {
+                (new PruneTask()).execute();
+            }
+        }
+    }
+
     void finishSessionLocked(int sessionId, int uid) {
         if (!isEnabled()) {
             return;
@@ -513,6 +541,7 @@ final class AutofillManagerServiceImpl {
         pw.print(prefix); pw.print("Default component: ");
             pw.println(mContext.getString(R.string.config_defaultAutofillService));
         pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled);
+        pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune);
 
         final int size = mSessions.size();
         if (size == 0) {
@@ -604,4 +633,62 @@ final class AutofillManagerServiceImpl {
                 + ", component=" + (mInfo != null
                 ? mInfo.getServiceInfo().getComponentName() : null) + "]";
     }
+
+    /** Task used to prune abandoned session */
+    private class PruneTask extends AsyncTask<Void, Void, Void> {
+        @Override
+        protected Void doInBackground(Void... ignored) {
+            int numSessionsToRemove;
+            ArrayMap<IBinder, Integer> sessionsToRemove;
+
+            synchronized (mLock) {
+                numSessionsToRemove = mSessions.size();
+                sessionsToRemove = new ArrayMap<>(numSessionsToRemove);
+
+                for (int i = 0; i < numSessionsToRemove; i++) {
+                    Session session = mSessions.valueAt(i);
+
+                    sessionsToRemove.put(session.getActivityTokenLocked(), session.id);
+                }
+            }
+
+            IActivityManager am = ActivityManager.getService();
+
+            // Only remove sessions which's activities are not known to the activity manager anymore
+            for (int i = 0; i < numSessionsToRemove; i++) {
+                try {
+                    // The activity manager cannot resolve activities that have been removed
+                    if (am.getActivityClassForToken(sessionsToRemove.keyAt(i)) != null) {
+                        sessionsToRemove.removeAt(i);
+                        i--;
+                        numSessionsToRemove--;
+                    }
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Cannot figure out if activity is finished", e);
+                }
+            }
+
+            synchronized (mLock) {
+                for (int i = 0; i < numSessionsToRemove; i++) {
+                    Session sessionToRemove = mSessions.get(sessionsToRemove.valueAt(i));
+
+                    if (sessionToRemove != null) {
+                        if (sessionToRemove.isSavingLocked()) {
+                            if (sVerbose) {
+                                Slog.v(TAG, "Session " + sessionToRemove.id + " is saving");
+                            }
+                        } else {
+                            if (sDebug) {
+                                Slog.i(TAG, "Prune session " + sessionToRemove.id + " ("
+                                    + sessionToRemove.getActivityTokenLocked() + ")");
+                            }
+                            sessionToRemove.removeSelfLocked();
+                        }
+                    }
+                }
+            }
+
+            return null;
+        }
+    }
 }
index dbc7f2b..3c85034 100644 (file)
@@ -177,6 +177,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
     @GuardedBy("mLock")
     private boolean mDestroyed;
 
+    /** Whether the session is currently saving */
+    @GuardedBy("mLock")
+    private boolean mIsSaving;
+
     /**
      * Receiver of assist data from the app's {@link Activity}.
      */
@@ -361,7 +365,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
      *
      * @return The activity token
      */
-    IBinder getActivityTokenLocked() {
+    @NonNull IBinder getActivityTokenLocked() {
         return mActivityToken;
     }
 
@@ -474,6 +478,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
     @Override
     public void onSaveRequestSuccess(@NonNull String servicePackageName) {
         synchronized (mLock) {
+            mIsSaving = false;
+
             if (mDestroyed) {
                 Slog.w(TAG, "Call to Session#onSaveRequestSuccess() rejected - session: "
                         + id + " destroyed");
@@ -496,6 +502,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
     public void onSaveRequestFailure(@Nullable CharSequence message,
             @NonNull String servicePackageName) {
         synchronized (mLock) {
+            mIsSaving = false;
+
             if (mDestroyed) {
                 Slog.w(TAG, "Call to Session#onSaveRequestFailure() rejected - session: "
                         + id + " destroyed");
@@ -596,6 +604,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
     @Override
     public void cancelSave() {
         synchronized (mLock) {
+            mIsSaving = false;
+
             if (mDestroyed) {
                 Slog.w(TAG, "Call to Session#cancelSave() rejected - session: "
                         + id + " destroyed");
@@ -831,6 +841,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
             if (atLeastOneChanged) {
                 mService.setSaveShown();
                 getUiForShowing().showSaveUi(mService.getServiceLabel(), saveInfo, mPackageName);
+
+                mIsSaving = true;
                 return false;
             }
         }
@@ -844,6 +856,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
     }
 
     /**
+     * Returns whether the session is currently showing the save UI
+     */
+    boolean isSavingLocked() {
+        return mIsSaving;
+    }
+
+    /**
      * Calls service when user requested save.
      */
     void callSaveLocked() {
@@ -1349,6 +1368,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
         pw.print(prefix); pw.print("mCurrentViewId: "); pw.println(mCurrentViewId);
         pw.print(prefix); pw.print("mViewStates size: "); pw.println(mViewStates.size());
         pw.print(prefix); pw.print("mDestroyed: "); pw.println(mDestroyed);
+        pw.print(prefix); pw.print("mIsSaving: "); pw.println(mIsSaving);
         final String prefix2 = prefix + "  ";
         for (Map.Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) {
             pw.print(prefix); pw.print("State for id "); pw.println(entry.getKey());
index 25814b3..7428460 100644 (file)
@@ -264,6 +264,10 @@ public final class AutoFillUI {
                 public void onDestroy() {
                     if (log.getType() == MetricsProto.MetricsEvent.TYPE_UNKNOWN) {
                         log.setType(MetricsProto.MetricsEvent.TYPE_CLOSE);
+
+                        if (mCallback != null) {
+                            mCallback.cancelSave();
+                        }
                     }
                     mMetricsLogger.write(log);
                 }