OSDN Git Service

Fixing session abandon logging bug
authorJan Althaus <jalt@google.com>
Fri, 22 Sep 2017 16:26:06 +0000 (18:26 +0200)
committerJan Althaus <jalt@google.com>
Mon, 25 Sep 2017 08:37:24 +0000 (10:37 +0200)
Previously, making two selections quickly one after the other could lead
to the first session missing the terminal event, and the second one getting
terminated prematurely - getting marked incorrectly as abandoned.

Bug: 64914512
Test: Manually tested that logs are correct.
Change-Id: Icd75dcabe707b591f30629b9b9b42c5459ed7dda

core/java/android/widget/SelectionActionModeHelper.java

index 4ebb3cf..fceefaf 100644 (file)
@@ -49,6 +49,8 @@ import java.util.regex.Pattern;
 @UiThread
 final class SelectionActionModeHelper {
 
+    private static final String LOG_TAG = "SelectActionModeHelper";
+
     /**
      * Maximum time (in milliseconds) to wait for a result before timing out.
      */
@@ -216,6 +218,7 @@ final class SelectionActionModeHelper {
         private int mSelectionStart;
         private int mSelectionEnd;
         private boolean mAllowReset;
+        private final LogAbandonRunnable mDelayedLogAbandon = new LogAbandonRunnable();
 
         SelectionTracker(TextView textView) {
             mTextView = Preconditions.checkNotNull(textView);
@@ -227,6 +230,10 @@ final class SelectionActionModeHelper {
          */
         public void onOriginalSelection(
                 CharSequence text, int selectionStart, int selectionEnd, boolean editableText) {
+            // If we abandoned a selection and created a new one very shortly after, we may still
+            // have a pending request to log ABANDON, which we flush here.
+            mDelayedLogAbandon.flush();
+
             mOriginalStart = mSelectionStart = selectionStart;
             mOriginalEnd = mSelectionEnd = selectionEnd;
             mAllowReset = false;
@@ -267,12 +274,7 @@ final class SelectionActionModeHelper {
         public void onSelectionDestroyed() {
             mAllowReset = false;
             // Wait a few ms to see if the selection was destroyed because of a text change event.
-            mTextView.postDelayed(() -> {
-                mLogger.logSelectionAction(
-                        mSelectionStart, mSelectionEnd,
-                        SelectionEvent.ActionType.ABANDON, null /* classification */);
-                mSelectionStart = mSelectionEnd = -1;
-            }, 100 /* ms */);
+            mDelayedLogAbandon.schedule(100 /* ms */);
         }
 
         /**
@@ -329,6 +331,38 @@ final class SelectionActionModeHelper {
         private boolean isSelectionStarted() {
             return mSelectionStart >= 0 && mSelectionEnd >= 0 && mSelectionStart != mSelectionEnd;
         }
+
+        /** A helper for keeping track of pending abandon logging requests. */
+        private final class LogAbandonRunnable implements Runnable {
+            private boolean mIsPending;
+
+            /** Schedules an abandon to be logged with the given delay. Flush if necessary. */
+            void schedule(int delayMillis) {
+                if (mIsPending) {
+                    Log.e(LOG_TAG, "Force flushing abandon due to new scheduling request");
+                    flush();
+                }
+                mIsPending = true;
+                mTextView.postDelayed(this, delayMillis);
+            }
+
+            /** If there is a pending log request, execute it now. */
+            void flush() {
+                mTextView.removeCallbacks(this);
+                run();
+            }
+
+            @Override
+            public void run() {
+                if (mIsPending) {
+                    mLogger.logSelectionAction(
+                            mSelectionStart, mSelectionEnd,
+                            SelectionEvent.ActionType.ABANDON, null /* classification */);
+                    mSelectionStart = mSelectionEnd = -1;
+                    mIsPending = false;
+                }
+            }
+        }
     }
 
     // TODO: Write tests