ListSelectionManager.java

  1package eu.siacs.conversations.ui.widget;
  2
  3import java.lang.reflect.Field;
  4import java.lang.reflect.Method;
  5
  6import android.os.Handler;
  7import android.os.Looper;
  8import android.os.Message;
  9import android.text.Selection;
 10import android.text.Spannable;
 11import android.view.ActionMode;
 12import android.view.Menu;
 13import android.view.MenuItem;
 14import android.widget.TextView;
 15
 16public class ListSelectionManager {
 17
 18	private static final int MESSAGE_SEND_RESET = 1;
 19	private static final int MESSAGE_RESET = 2;
 20	private static final int MESSAGE_START_SELECTION = 3;
 21
 22	private static final Handler HANDLER = new Handler(Looper.getMainLooper(), new Handler.Callback() {
 23
 24		@Override
 25		public boolean handleMessage(Message msg) {
 26			switch (msg.what) {
 27				case MESSAGE_SEND_RESET: {
 28					// Skip one more message queue loop
 29					HANDLER.obtainMessage(MESSAGE_RESET, msg.obj).sendToTarget();
 30					return true;
 31				}
 32				case MESSAGE_RESET: {
 33					final ListSelectionManager listSelectionManager = (ListSelectionManager) msg.obj;
 34					listSelectionManager.futureSelectionIdentifier = null;
 35					return true;
 36				}
 37				case MESSAGE_START_SELECTION: {
 38					final StartSelectionHolder holder = (StartSelectionHolder) msg.obj;
 39					holder.listSelectionManager.futureSelectionIdentifier = null;
 40					startSelection(holder.textView, holder.start, holder.end);
 41					return true;
 42				}
 43			}
 44			return false;
 45		}
 46	});
 47
 48	private static class StartSelectionHolder {
 49
 50		public final ListSelectionManager listSelectionManager;
 51		public final TextView textView;
 52		public final int start;
 53		public final int end;
 54
 55		public StartSelectionHolder(ListSelectionManager listSelectionManager, TextView textView,
 56				int start, int end) {
 57			this.listSelectionManager = listSelectionManager;
 58			this.textView = textView;
 59			this.start = start;
 60			this.end = end;
 61		}
 62	}
 63
 64	private ActionMode selectionActionMode;
 65	private Object selectionIdentifier;
 66	private TextView selectionTextView;
 67
 68	private Object futureSelectionIdentifier;
 69	private int futureSelectionStart;
 70	private int futureSelectionEnd;
 71
 72	public void onCreate(TextView textView) {
 73		final CustomCallback callback = new CustomCallback(textView);
 74		textView.setCustomSelectionActionModeCallback(callback);
 75	}
 76
 77	public void onUpdate(TextView textView, Object identifier) {
 78		if (SUPPORTED) {
 79			CustomCallback callback = (CustomCallback) textView.getCustomSelectionActionModeCallback();
 80			callback.identifier = identifier;
 81			if (futureSelectionIdentifier == identifier) {
 82				HANDLER.obtainMessage(MESSAGE_START_SELECTION, new StartSelectionHolder(this,
 83						textView, futureSelectionStart, futureSelectionEnd)).sendToTarget();
 84			}
 85		}
 86	}
 87
 88	public void onBeforeNotifyDataSetChanged() {
 89		if (SUPPORTED) {
 90			HANDLER.removeMessages(MESSAGE_SEND_RESET);
 91			HANDLER.removeMessages(MESSAGE_RESET);
 92			HANDLER.removeMessages(MESSAGE_START_SELECTION);
 93			if (selectionActionMode != null) {
 94				final CharSequence text = selectionTextView.getText();
 95				futureSelectionIdentifier = selectionIdentifier;
 96				futureSelectionStart = Selection.getSelectionStart(text);
 97				futureSelectionEnd = Selection.getSelectionEnd(text);
 98				selectionActionMode.finish();
 99				selectionActionMode = null;
100				selectionIdentifier = null;
101				selectionTextView = null;
102			}
103		}
104	}
105
106	public void onAfterNotifyDataSetChanged() {
107		if (SUPPORTED && futureSelectionIdentifier != null) {
108			HANDLER.obtainMessage(MESSAGE_SEND_RESET, this).sendToTarget();
109		}
110	}
111
112	private class CustomCallback implements ActionMode.Callback {
113
114		private final TextView textView;
115		public Object identifier;
116
117		public CustomCallback(TextView textView) {
118			this.textView = textView;
119		}
120
121		@Override
122		public boolean onCreateActionMode(ActionMode mode, Menu menu) {
123			selectionActionMode = mode;
124			selectionIdentifier = identifier;
125			selectionTextView = textView;
126			return true;
127		}
128
129		@Override
130		public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
131			return true;
132		}
133
134		@Override
135		public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
136			return false;
137		}
138
139		@Override
140		public void onDestroyActionMode(ActionMode mode) {
141			if (selectionActionMode == mode) {
142				selectionActionMode = null;
143				selectionIdentifier = null;
144				selectionTextView = null;
145			}
146		}
147	}
148
149	private static final Field FIELD_EDITOR;
150	private static final Method METHOD_START_SELECTION;
151	private static final boolean SUPPORTED;
152
153	static {
154		Field editor;
155		try {
156			editor = TextView.class.getDeclaredField("mEditor");
157			editor.setAccessible(true);
158		} catch (Exception e) {
159			editor = null;
160		}
161		FIELD_EDITOR = editor;
162		Method startSelection = null;
163		if (editor != null) {
164			String[] startSelectionNames = {"startSelectionActionMode", "startSelectionActionModeWithSelection"};
165			for (String startSelectionName : startSelectionNames) {
166				try {
167					startSelection = editor.getType().getDeclaredMethod(startSelectionName);
168					startSelection.setAccessible(true);
169					break;
170				} catch (Exception e) {
171					startSelection = null;
172				}
173			}
174		}
175		METHOD_START_SELECTION = startSelection;
176		SUPPORTED = FIELD_EDITOR != null && METHOD_START_SELECTION != null;
177	}
178
179	public static boolean isSupported() {
180		return SUPPORTED;
181	}
182
183	public static void startSelection(TextView textView) {
184		startSelection(textView, 0, textView.getText().length());
185	}
186
187	public static void startSelection(TextView textView, int start, int end) {
188		final CharSequence text = textView.getText();
189		if (SUPPORTED && start >= 0 && end > start && textView.isTextSelectable() && text instanceof Spannable) {
190			final Spannable spannable = (Spannable) text;
191			start = Math.min(start, spannable.length());
192			end = Math.min(end, spannable.length());
193			Selection.setSelection(spannable, start, end);
194			try {
195				final Object editor = FIELD_EDITOR != null ? FIELD_EDITOR.get(textView) : textView;
196				METHOD_START_SELECTION.invoke(editor);
197			} catch (Exception e) {
198			}
199		}
200	}
201}