Automatic persistent text selection for ListViews
Use View transient state tracking to allow selection to persist across
ListView-style item view recycling.
Fix some bugs with transient state tracking.
Bug 6110122
Change-Id: Ic084b8fc2289bff718b19478a37ce64459b3ed4c
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index bf7d037..c20351b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5570,7 +5570,7 @@
"unmatched pair of setHasTransientState calls");
}
if ((hasTransientState && mTransientStateCount == 1) ||
- (hasTransientState && mTransientStateCount == 0)) {
+ (!hasTransientState && mTransientStateCount == 0)) {
// update flag if we've just incremented up from 0 or decremented down to 0
mPrivateFlags2 = (mPrivateFlags2 & ~HAS_TRANSIENT_STATE) |
(hasTransientState ? HAS_TRANSIENT_STATE : 0);
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index c4e1bf5..3b4ec7d 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -6317,6 +6317,7 @@
if (mTransientStateViews == null) {
mTransientStateViews = new SparseArray<View>();
}
+ scrap.dispatchStartTemporaryDetach();
mTransientStateViews.put(position, scrap);
}
return;
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 4b7ec9a..16490e8 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -16,6 +16,9 @@
package android.widget;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.widget.EditableInputConnection;
+
import android.R;
import android.content.ClipData;
import android.content.ClipData.Item;
@@ -70,10 +73,10 @@
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
import android.view.View.DragShadowBuilder;
import android.view.View.OnClickListener;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewParent;
import android.view.ViewTreeObserver;
@@ -85,12 +88,12 @@
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView.OnItemClickListener;
+import android.widget.Editor.InputContentType;
+import android.widget.Editor.InputMethodState;
+import android.widget.Editor.SelectionModifierCursorController;
import android.widget.TextView.Drawables;
import android.widget.TextView.OnEditorActionListener;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.widget.EditableInputConnection;
-
import java.text.BreakIterator;
import java.util.Arrays;
import java.util.Comparator;
@@ -102,6 +105,8 @@
* @hide
*/
public class Editor {
+ private static final String TAG = "Editor";
+
static final int BLINK = 500;
private static final float[] TEMP_POSITION = new float[2];
private static int DRAG_SHADOW_MAX_TEXT_LENGTH = 20;
@@ -151,6 +156,8 @@
boolean mInBatchEditControllers;
boolean mShowSoftInputOnFocus = true;
+ boolean mPreserveDetachedSelection;
+ boolean mTemporaryDetach;
SuggestionsPopupWindow mSuggestionsPopupWindow;
SuggestionRangeSpan mSuggestionRangeSpan;
@@ -190,6 +197,7 @@
showError();
mShowErrorAfterAttach = false;
}
+ mTemporaryDetach = false;
final ViewTreeObserver observer = mTextView.getViewTreeObserver();
// No need to create the controller.
@@ -198,10 +206,22 @@
observer.addOnTouchModeChangeListener(mInsertionPointCursorController);
}
if (mSelectionModifierCursorController != null) {
+ mSelectionModifierCursorController.resetTouchOffsets();
observer.addOnTouchModeChangeListener(mSelectionModifierCursorController);
}
updateSpellCheckSpans(0, mTextView.getText().length(),
true /* create the spell checker if needed */);
+
+ if (mTextView.hasTransientState() &&
+ mTextView.getSelectionStart() != mTextView.getSelectionEnd()) {
+ // Since transient state is reference counted make sure it stays matched
+ // with our own calls to it for managing selection.
+ // The action mode callback will set this back again when/if the action mode starts.
+ mTextView.setHasTransientState(false);
+
+ // We had an active selection from before, start the selection mode.
+ startSelectionActionMode();
+ }
}
void onDetachedFromWindow() {
@@ -234,7 +254,10 @@
mSpellChecker = null;
}
+ mPreserveDetachedSelection = true;
hideControllers();
+ mPreserveDetachedSelection = false;
+ mTemporaryDetach = false;
}
private void showError() {
@@ -877,7 +900,9 @@
hideControllers();
Selection.setSelection((Spannable) mTextView.getText(), selStart, selEnd);
} else {
+ if (mTemporaryDetach) mPreserveDetachedSelection = true;
hideControllers();
+ if (mTemporaryDetach) mPreserveDetachedSelection = false;
downgradeEasyCorrectionSpans();
}
@@ -2679,6 +2704,7 @@
if (menu.hasVisibleItems() || mode.getCustomView() != null) {
getSelectionController().show();
+ mTextView.setHasTransientState(true);
return true;
} else {
return false;
@@ -2707,7 +2733,17 @@
if (mCustomSelectionActionModeCallback != null) {
mCustomSelectionActionModeCallback.onDestroyActionMode(mode);
}
- Selection.setSelection((Spannable) mTextView.getText(), mTextView.getSelectionEnd());
+
+ /*
+ * If we're ending this mode because we're detaching from a window,
+ * we still have selection state to preserve. Don't clear it, we'll
+ * bring back the selection mode when (if) we get reattached.
+ */
+ if (!mPreserveDetachedSelection) {
+ Selection.setSelection((Spannable) mTextView.getText(),
+ mTextView.getSelectionEnd());
+ mTextView.setHasTransientState(false);
+ }
if (mSelectionModifierCursorController != null) {
mSelectionModifierCursorController.hide();
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 098a034..56eca01 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -7257,10 +7257,9 @@
// usually because this instance is an editable field in a list
if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
- // Because of View recycling in ListView, there is no easy way to know when a TextView with
- // selection becomes visible again. Until a better solution is found, stop text selection
- // mode (if any) as soon as this TextView is recycled.
- if (mEditor != null) mEditor.hideControllers();
+ // Tell the editor that we are temporarily detached. It can use this to preserve
+ // selection state as needed.
+ if (mEditor != null) mEditor.mTemporaryDetach = true;
}
@Override
@@ -7269,6 +7268,7 @@
// Only track when onStartTemporaryDetach() is called directly,
// usually because this instance is an editable field in a list
if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
+ if (mEditor != null) mEditor.mTemporaryDetach = false;
}
@Override