Merge "Fixing session abandon logging bug" into oc-mr1-dev am: 4fce88dd4d
am: c7812f0fa5

Change-Id: I57abb3d7f4bce7cdda696b57f65475c02ac3b498
diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java
index 36dc330..1b93352 100644
--- a/core/java/android/widget/SelectionActionModeHelper.java
+++ b/core/java/android/widget/SelectionActionModeHelper.java
@@ -58,6 +58,8 @@
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
 public final class SelectionActionModeHelper {
 
+    private static final String LOG_TAG = "SelectActionModeHelper";
+
     /**
      * Maximum time (in milliseconds) to wait for a result before timing out.
      */
@@ -382,6 +384,7 @@
         private int mSelectionStart;
         private int mSelectionEnd;
         private boolean mAllowReset;
+        private final LogAbandonRunnable mDelayedLogAbandon = new LogAbandonRunnable();
 
         SelectionTracker(TextView textView) {
             mTextView = Preconditions.checkNotNull(textView);
@@ -393,6 +396,10 @@
          */
         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;
@@ -433,12 +440,7 @@
         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 */);
         }
 
         /**
@@ -495,6 +497,38 @@
         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