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}