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, ActionMode.Callback additionalCallback) {
 73		final CustomCallback callback = new CustomCallback(textView, additionalCallback);
 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		private final ActionMode.Callback additionalCallback;
116		public Object identifier;
117
118		public CustomCallback(TextView textView, ActionMode.Callback additionalCallback) {
119			this.textView = textView;
120			this.additionalCallback = additionalCallback;
121		}
122
123		@Override
124		public boolean onCreateActionMode(ActionMode mode, Menu menu) {
125			selectionActionMode = mode;
126			selectionIdentifier = identifier;
127			selectionTextView = textView;
128			if (additionalCallback != null) {
129				additionalCallback.onCreateActionMode(mode, menu);
130			}
131			return true;
132		}
133
134		@Override
135		public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
136			if (additionalCallback != null) {
137				additionalCallback.onPrepareActionMode(mode, menu);
138			}
139			return true;
140		}
141
142		@Override
143		public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
144			if (additionalCallback != null && additionalCallback.onActionItemClicked(mode, item)) {
145				return true;
146			}
147			return false;
148		}
149
150		@Override
151		public void onDestroyActionMode(ActionMode mode) {
152			if (additionalCallback != null) {
153				additionalCallback.onDestroyActionMode(mode);
154			}
155			if (selectionActionMode == mode) {
156				selectionActionMode = null;
157				selectionIdentifier = null;
158				selectionTextView = null;
159			}
160		}
161	}
162
163	private static final Field FIELD_EDITOR;
164	private static final Method METHOD_START_SELECTION;
165	private static final boolean SUPPORTED;
166
167	static {
168		Field editor;
169		try {
170			editor = TextView.class.getDeclaredField("mEditor");
171			editor.setAccessible(true);
172		} catch (Exception e) {
173			editor = null;
174		}
175		FIELD_EDITOR = editor;
176		Method startSelection = null;
177		if (editor != null) {
178			String[] startSelectionNames = {"startSelectionActionMode", "startSelectionActionModeWithSelection"};
179			for (String startSelectionName : startSelectionNames) {
180				try {
181					startSelection = editor.getType().getDeclaredMethod(startSelectionName);
182					startSelection.setAccessible(true);
183					break;
184				} catch (Exception e) {
185					startSelection = null;
186				}
187			}
188		}
189		METHOD_START_SELECTION = startSelection;
190		SUPPORTED = FIELD_EDITOR != null && METHOD_START_SELECTION != null;
191	}
192
193	public static boolean isSupported() {
194		return SUPPORTED;
195	}
196
197	public static void startSelection(TextView textView, int start, int end) {
198		final CharSequence text = textView.getText();
199		if (SUPPORTED && start >= 0 && end > start && textView.isTextSelectable() && text instanceof Spannable) {
200			final Spannable spannable = (Spannable) text;
201			start = Math.min(start, spannable.length());
202			end = Math.min(end, spannable.length());
203			Selection.setSelection(spannable, start, end);
204			try {
205				final Object editor = FIELD_EDITOR != null ? FIELD_EDITOR.get(textView) : textView;
206				METHOD_START_SELECTION.invoke(editor);
207			} catch (Exception e) {
208			}
209		}
210	}
211}