make list selection manager work with app compat
This commit is contained in:
		
							parent
							
								
									f9e1e856d2
								
							
						
					
					
						commit
						11736ce48c
					
				| @ -1,8 +1,5 @@ | |||||||
| package eu.siacs.conversations.ui.widget; | package eu.siacs.conversations.ui.widget; | ||||||
| 
 | 
 | ||||||
| import java.lang.reflect.Field; |  | ||||||
| import java.lang.reflect.Method; |  | ||||||
| 
 |  | ||||||
| import android.os.Handler; | import android.os.Handler; | ||||||
| import android.os.Looper; | import android.os.Looper; | ||||||
| import android.os.Message; | import android.os.Message; | ||||||
| @ -13,199 +10,202 @@ import android.view.Menu; | |||||||
| import android.view.MenuItem; | import android.view.MenuItem; | ||||||
| import android.widget.TextView; | import android.widget.TextView; | ||||||
| 
 | 
 | ||||||
|  | import java.lang.reflect.Field; | ||||||
|  | import java.lang.reflect.Method; | ||||||
|  | 
 | ||||||
| public class ListSelectionManager { | public class ListSelectionManager { | ||||||
| 
 | 
 | ||||||
| 	private static final int MESSAGE_SEND_RESET = 1; |     private static final int MESSAGE_SEND_RESET = 1; | ||||||
| 	private static final int MESSAGE_RESET = 2; |     private static final int MESSAGE_RESET = 2; | ||||||
| 	private static final int MESSAGE_START_SELECTION = 3; |     private static final int MESSAGE_START_SELECTION = 3; | ||||||
|  |     private static final Field FIELD_EDITOR; | ||||||
|  |     private static final Method METHOD_START_SELECTION; | ||||||
|  |     private static final boolean SUPPORTED; | ||||||
|  |     private static final Handler HANDLER = new Handler(Looper.getMainLooper(), new Handler.Callback() { | ||||||
| 
 | 
 | ||||||
| 	private static final Handler HANDLER = new Handler(Looper.getMainLooper(), new Handler.Callback() { |         @Override | ||||||
|  |         public boolean handleMessage(Message msg) { | ||||||
|  |             switch (msg.what) { | ||||||
|  |                 case MESSAGE_SEND_RESET: { | ||||||
|  |                     // Skip one more message queue loop | ||||||
|  |                     HANDLER.obtainMessage(MESSAGE_RESET, msg.obj).sendToTarget(); | ||||||
|  |                     return true; | ||||||
|  |                 } | ||||||
|  |                 case MESSAGE_RESET: { | ||||||
|  |                     final ListSelectionManager listSelectionManager = (ListSelectionManager) msg.obj; | ||||||
|  |                     listSelectionManager.futureSelectionIdentifier = null; | ||||||
|  |                     return true; | ||||||
|  |                 } | ||||||
|  |                 case MESSAGE_START_SELECTION: { | ||||||
|  |                     final StartSelectionHolder holder = (StartSelectionHolder) msg.obj; | ||||||
|  |                     holder.listSelectionManager.futureSelectionIdentifier = null; | ||||||
|  |                     startSelection(holder.textView, holder.start, holder.end); | ||||||
|  |                     return true; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     }); | ||||||
| 
 | 
 | ||||||
| 		@Override |     static { | ||||||
| 		public boolean handleMessage(Message msg) { |         Field editor; | ||||||
| 			switch (msg.what) { |         try { | ||||||
| 				case MESSAGE_SEND_RESET: { |             editor = TextView.class.getDeclaredField("mEditor"); | ||||||
| 					// Skip one more message queue loop |             editor.setAccessible(true); | ||||||
| 					HANDLER.obtainMessage(MESSAGE_RESET, msg.obj).sendToTarget(); |         } catch (Exception e) { | ||||||
| 					return true; |             editor = null; | ||||||
| 				} |         } | ||||||
| 				case MESSAGE_RESET: { |         FIELD_EDITOR = editor; | ||||||
| 					final ListSelectionManager listSelectionManager = (ListSelectionManager) msg.obj; |         Method startSelection = null; | ||||||
| 					listSelectionManager.futureSelectionIdentifier = null; |         if (editor != null) { | ||||||
| 					return true; |             String[] startSelectionNames = {"startSelectionActionMode", "startSelectionActionModeWithSelection"}; | ||||||
| 				} |             for (String startSelectionName : startSelectionNames) { | ||||||
| 				case MESSAGE_START_SELECTION: { |                 try { | ||||||
| 					final StartSelectionHolder holder = (StartSelectionHolder) msg.obj; |                     startSelection = editor.getType().getDeclaredMethod(startSelectionName); | ||||||
| 					holder.listSelectionManager.futureSelectionIdentifier = null; |                     startSelection.setAccessible(true); | ||||||
| 					startSelection(holder.textView, holder.start, holder.end); |                     break; | ||||||
| 					return true; |                 } catch (Exception e) { | ||||||
| 				} |                     startSelection = null; | ||||||
| 			} |                 } | ||||||
| 			return false; |             } | ||||||
| 		} |         } | ||||||
| 	}); |         METHOD_START_SELECTION = startSelection; | ||||||
|  |         SUPPORTED = FIELD_EDITOR != null && METHOD_START_SELECTION != null; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| 	private static class StartSelectionHolder { |     private ActionMode selectionActionMode; | ||||||
|  |     private Object selectionIdentifier; | ||||||
|  |     private TextView selectionTextView; | ||||||
|  |     private Object futureSelectionIdentifier; | ||||||
|  |     private int futureSelectionStart; | ||||||
|  |     private int futureSelectionEnd; | ||||||
| 
 | 
 | ||||||
| 		public final ListSelectionManager listSelectionManager; |     public static boolean isSupported() { | ||||||
| 		public final TextView textView; |         return SUPPORTED; | ||||||
| 		public final int start; |     } | ||||||
| 		public final int end; |  | ||||||
| 
 | 
 | ||||||
| 		public StartSelectionHolder(ListSelectionManager listSelectionManager, TextView textView, |     private static void startSelection(TextView textView, int start, int end) { | ||||||
| 				int start, int end) { |         final CharSequence text = textView.getText(); | ||||||
| 			this.listSelectionManager = listSelectionManager; |         if (SUPPORTED && start >= 0 && end > start && textView.isTextSelectable() && text instanceof Spannable) { | ||||||
| 			this.textView = textView; |             final Spannable spannable = (Spannable) text; | ||||||
| 			this.start = start; |             start = Math.min(start, spannable.length()); | ||||||
| 			this.end = end; |             end = Math.min(end, spannable.length()); | ||||||
| 		} |             Selection.setSelection(spannable, start, end); | ||||||
| 	} |             try { | ||||||
|  |                 final Object editor = FIELD_EDITOR != null ? FIELD_EDITOR.get(textView) : textView; | ||||||
|  |                 METHOD_START_SELECTION.invoke(editor); | ||||||
|  |             } catch (Exception e) { | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| 	private ActionMode selectionActionMode; |     public void onCreate(TextView textView, ActionMode.Callback additionalCallback) { | ||||||
| 	private Object selectionIdentifier; |         final CustomCallback callback = new CustomCallback(textView, additionalCallback); | ||||||
| 	private TextView selectionTextView; |         textView.setCustomSelectionActionModeCallback(callback); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| 	private Object futureSelectionIdentifier; |     public void onUpdate(TextView textView, Object identifier) { | ||||||
| 	private int futureSelectionStart; |         if (SUPPORTED) { | ||||||
| 	private int futureSelectionEnd; |             final ActionMode.Callback callback = textView.getCustomSelectionActionModeCallback(); | ||||||
|  |             if (callback instanceof CustomCallback) { | ||||||
|  |                 final CustomCallback customCallback = (CustomCallback) textView.getCustomSelectionActionModeCallback(); | ||||||
|  |                 customCallback.identifier = identifier; | ||||||
|  |                 if (futureSelectionIdentifier == identifier) { | ||||||
|  |                     HANDLER.obtainMessage(MESSAGE_START_SELECTION, new StartSelectionHolder(this, | ||||||
|  |                             textView, futureSelectionStart, futureSelectionEnd)).sendToTarget(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| 	public void onCreate(TextView textView, ActionMode.Callback additionalCallback) { |     public void onBeforeNotifyDataSetChanged() { | ||||||
| 		final CustomCallback callback = new CustomCallback(textView, additionalCallback); |         if (SUPPORTED) { | ||||||
| 		textView.setCustomSelectionActionModeCallback(callback); |             HANDLER.removeMessages(MESSAGE_SEND_RESET); | ||||||
| 	} |             HANDLER.removeMessages(MESSAGE_RESET); | ||||||
|  |             HANDLER.removeMessages(MESSAGE_START_SELECTION); | ||||||
|  |             if (selectionActionMode != null) { | ||||||
|  |                 final CharSequence text = selectionTextView.getText(); | ||||||
|  |                 futureSelectionIdentifier = selectionIdentifier; | ||||||
|  |                 futureSelectionStart = Selection.getSelectionStart(text); | ||||||
|  |                 futureSelectionEnd = Selection.getSelectionEnd(text); | ||||||
|  |                 selectionActionMode.finish(); | ||||||
|  |                 selectionActionMode = null; | ||||||
|  |                 selectionIdentifier = null; | ||||||
|  |                 selectionTextView = null; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| 	public void onUpdate(TextView textView, Object identifier) { |     public void onAfterNotifyDataSetChanged() { | ||||||
| 		if (SUPPORTED) { |         if (SUPPORTED && futureSelectionIdentifier != null) { | ||||||
| 			CustomCallback callback = (CustomCallback) textView.getCustomSelectionActionModeCallback(); |             HANDLER.obtainMessage(MESSAGE_SEND_RESET, this).sendToTarget(); | ||||||
| 			callback.identifier = identifier; |         } | ||||||
| 			if (futureSelectionIdentifier == identifier) { |     } | ||||||
| 				HANDLER.obtainMessage(MESSAGE_START_SELECTION, new StartSelectionHolder(this, |  | ||||||
| 						textView, futureSelectionStart, futureSelectionEnd)).sendToTarget(); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	public void onBeforeNotifyDataSetChanged() { |     private static class StartSelectionHolder { | ||||||
| 		if (SUPPORTED) { |  | ||||||
| 			HANDLER.removeMessages(MESSAGE_SEND_RESET); |  | ||||||
| 			HANDLER.removeMessages(MESSAGE_RESET); |  | ||||||
| 			HANDLER.removeMessages(MESSAGE_START_SELECTION); |  | ||||||
| 			if (selectionActionMode != null) { |  | ||||||
| 				final CharSequence text = selectionTextView.getText(); |  | ||||||
| 				futureSelectionIdentifier = selectionIdentifier; |  | ||||||
| 				futureSelectionStart = Selection.getSelectionStart(text); |  | ||||||
| 				futureSelectionEnd = Selection.getSelectionEnd(text); |  | ||||||
| 				selectionActionMode.finish(); |  | ||||||
| 				selectionActionMode = null; |  | ||||||
| 				selectionIdentifier = null; |  | ||||||
| 				selectionTextView = null; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	public void onAfterNotifyDataSetChanged() { |         final ListSelectionManager listSelectionManager; | ||||||
| 		if (SUPPORTED && futureSelectionIdentifier != null) { |         final TextView textView; | ||||||
| 			HANDLER.obtainMessage(MESSAGE_SEND_RESET, this).sendToTarget(); |         public final int start; | ||||||
| 		} |         public final int end; | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	private class CustomCallback implements ActionMode.Callback { |         StartSelectionHolder(ListSelectionManager listSelectionManager, TextView textView, | ||||||
|  |                                     int start, int end) { | ||||||
|  |             this.listSelectionManager = listSelectionManager; | ||||||
|  |             this.textView = textView; | ||||||
|  |             this.start = start; | ||||||
|  |             this.end = end; | ||||||
|  |         } | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| 		private final TextView textView; |     private class CustomCallback implements ActionMode.Callback { | ||||||
| 		private final ActionMode.Callback additionalCallback; |  | ||||||
| 		public Object identifier; |  | ||||||
| 
 | 
 | ||||||
| 		public CustomCallback(TextView textView, ActionMode.Callback additionalCallback) { |         private final TextView textView; | ||||||
| 			this.textView = textView; |         private final ActionMode.Callback additionalCallback; | ||||||
| 			this.additionalCallback = additionalCallback; |         Object identifier; | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		@Override |         CustomCallback(TextView textView, ActionMode.Callback additionalCallback) { | ||||||
| 		public boolean onCreateActionMode(ActionMode mode, Menu menu) { |             this.textView = textView; | ||||||
| 			selectionActionMode = mode; |             this.additionalCallback = additionalCallback; | ||||||
| 			selectionIdentifier = identifier; |         } | ||||||
| 			selectionTextView = textView; |  | ||||||
| 			if (additionalCallback != null) { |  | ||||||
| 				additionalCallback.onCreateActionMode(mode, menu); |  | ||||||
| 			} |  | ||||||
| 			return true; |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		@Override |         @Override | ||||||
| 		public boolean onPrepareActionMode(ActionMode mode, Menu menu) { |         public boolean onCreateActionMode(ActionMode mode, Menu menu) { | ||||||
| 			if (additionalCallback != null) { |             selectionActionMode = mode; | ||||||
| 				additionalCallback.onPrepareActionMode(mode, menu); |             selectionIdentifier = identifier; | ||||||
| 			} |             selectionTextView = textView; | ||||||
| 			return true; |             if (additionalCallback != null) { | ||||||
| 		} |                 additionalCallback.onCreateActionMode(mode, menu); | ||||||
|  |             } | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
| 		@Override |         @Override | ||||||
| 		public boolean onActionItemClicked(ActionMode mode, MenuItem item) { |         public boolean onPrepareActionMode(ActionMode mode, Menu menu) { | ||||||
| 			if (additionalCallback != null && additionalCallback.onActionItemClicked(mode, item)) { |             if (additionalCallback != null) { | ||||||
| 				return true; |                 additionalCallback.onPrepareActionMode(mode, menu); | ||||||
| 			} |             } | ||||||
| 			return false; |             return true; | ||||||
| 		} |         } | ||||||
| 
 | 
 | ||||||
| 		@Override |         @Override | ||||||
| 		public void onDestroyActionMode(ActionMode mode) { |         public boolean onActionItemClicked(ActionMode mode, MenuItem item) { | ||||||
| 			if (additionalCallback != null) { |             if (additionalCallback != null && additionalCallback.onActionItemClicked(mode, item)) { | ||||||
| 				additionalCallback.onDestroyActionMode(mode); |                 return true; | ||||||
| 			} |             } | ||||||
| 			if (selectionActionMode == mode) { |             return false; | ||||||
| 				selectionActionMode = null; |         } | ||||||
| 				selectionIdentifier = null; |  | ||||||
| 				selectionTextView = null; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	private static final Field FIELD_EDITOR; |         @Override | ||||||
| 	private static final Method METHOD_START_SELECTION; |         public void onDestroyActionMode(ActionMode mode) { | ||||||
| 	private static final boolean SUPPORTED; |             if (additionalCallback != null) { | ||||||
| 
 |                 additionalCallback.onDestroyActionMode(mode); | ||||||
| 	static { |             } | ||||||
| 		Field editor; |             if (selectionActionMode == mode) { | ||||||
| 		try { |                 selectionActionMode = null; | ||||||
| 			editor = TextView.class.getDeclaredField("mEditor"); |                 selectionIdentifier = null; | ||||||
| 			editor.setAccessible(true); |                 selectionTextView = null; | ||||||
| 		} catch (Exception e) { |             } | ||||||
| 			editor = null; |         } | ||||||
| 		} |     } | ||||||
| 		FIELD_EDITOR = editor; |  | ||||||
| 		Method startSelection = null; |  | ||||||
| 		if (editor != null) { |  | ||||||
| 			String[] startSelectionNames = {"startSelectionActionMode", "startSelectionActionModeWithSelection"}; |  | ||||||
| 			for (String startSelectionName : startSelectionNames) { |  | ||||||
| 				try { |  | ||||||
| 					startSelection = editor.getType().getDeclaredMethod(startSelectionName); |  | ||||||
| 					startSelection.setAccessible(true); |  | ||||||
| 					break; |  | ||||||
| 				} catch (Exception e) { |  | ||||||
| 					startSelection = null; |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		METHOD_START_SELECTION = startSelection; |  | ||||||
| 		SUPPORTED = FIELD_EDITOR != null && METHOD_START_SELECTION != null; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	public static boolean isSupported() { |  | ||||||
| 		return SUPPORTED; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	public static void startSelection(TextView textView, int start, int end) { |  | ||||||
| 		final CharSequence text = textView.getText(); |  | ||||||
| 		if (SUPPORTED && start >= 0 && end > start && textView.isTextSelectable() && text instanceof Spannable) { |  | ||||||
| 			final Spannable spannable = (Spannable) text; |  | ||||||
| 			start = Math.min(start, spannable.length()); |  | ||||||
| 			end = Math.min(end, spannable.length()); |  | ||||||
| 			Selection.setSelection(spannable, start, end); |  | ||||||
| 			try { |  | ||||||
| 				final Object editor = FIELD_EDITOR != null ? FIELD_EDITOR.get(textView) : textView; |  | ||||||
| 				METHOD_START_SELECTION.invoke(editor); |  | ||||||
| 			} catch (Exception e) { |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Daniel Gultsch
						Daniel Gultsch