made download button in message adapter work

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/ui/ConversationFragment.java   |  12 
src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java | 420 
2 files changed, 194 insertions(+), 238 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/ui/ConversationFragment.java 🔗

@@ -1471,6 +1471,18 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
 		activity.xmppConnectionService.getHttpConnectionManager().createNewDownloadConnection(message, true);
 	}
 
+	public static void downloadFile(Activity activity, Message message) {
+		Fragment fragment = activity.getFragmentManager().findFragmentById(R.id.main_fragment);
+		if (fragment != null && fragment instanceof ConversationFragment) {
+			((ConversationFragment) fragment).downloadFile(message);
+			return;
+		}
+		fragment = activity.getFragmentManager().findFragmentById(R.id.secondary_fragment);
+		if (fragment != null && fragment instanceof ConversationFragment) {
+			((ConversationFragment) fragment).downloadFile(message);
+		}
+	}
+
 	private void cancelTransmission(Message message) {
 		Transferable transferable = message.getTransferable();
 		if (transferable != null) {

src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java 🔗

@@ -63,6 +63,7 @@ import eu.siacs.conversations.entities.Transferable;
 import eu.siacs.conversations.persistance.FileBackend;
 import eu.siacs.conversations.services.MessageArchiveService;
 import eu.siacs.conversations.services.NotificationService;
+import eu.siacs.conversations.ui.ConversationFragment;
 import eu.siacs.conversations.ui.XmppActivity;
 import eu.siacs.conversations.ui.service.AudioPlayer;
 import eu.siacs.conversations.ui.text.DividerSpan;
@@ -83,13 +84,11 @@ import eu.siacs.conversations.xmpp.mam.MamReference;
 
 public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextView.CopyHandler {
 
+	public static final String DATE_SEPARATOR_BODY = "DATE_SEPARATOR";
 	private static final int SENT = 0;
 	private static final int RECEIVED = 1;
 	private static final int STATUS = 2;
 	private static final int DATE_SEPARATOR = 3;
-
-	public static final String DATE_SEPARATOR_BODY = "DATE_SEPARATOR";
-
 	private static final Pattern XMPP_PATTERN = Pattern
 			.compile("xmpp\\:(?:(?:["
 					+ Patterns.GOOD_IRI_CHAR
@@ -106,41 +105,35 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 			if (lcUrl.startsWith("http://") || lcUrl.startsWith("https://")) {
 				return url;
 			} else {
-				return "http://"+url;
+				return "http://" + url;
 			}
 		}
 	};
 	private static final Linkify.MatchFilter WEBURL_MATCH_FILTER = new Linkify.MatchFilter() {
 		@Override
 		public boolean acceptMatch(CharSequence cs, int start, int end) {
-			return start < 1 || (cs.charAt(start-1) != '@' && cs.charAt(start-1) != '.' && !cs.subSequence(Math.max(0,start - 3),start).equals("://"));
+			return start < 1 || (cs.charAt(start - 1) != '@' && cs.charAt(start - 1) != '.' && !cs.subSequence(Math.max(0, start - 3), start).equals("://"));
 		}
 	};
 
 	private static final Linkify.MatchFilter XMPPURI_MATCH_FILTER = new Linkify.MatchFilter() {
 		@Override
 		public boolean acceptMatch(CharSequence s, int start, int end) {
-			XmppUri uri = new XmppUri(s.subSequence(start,end).toString());
+			XmppUri uri = new XmppUri(s.subSequence(start, end).toString());
 			return uri.isJidValid();
 		}
 	};
 
 	private final XmppActivity activity;
-
+	private final ListSelectionManager listSelectionManager = new ListSelectionManager();
+	private final AudioPlayer audioPlayer;
 	private DisplayMetrics metrics;
-
 	private OnContactPictureClicked mOnContactPictureClickedListener;
 	private OnContactPictureLongClicked mOnContactPictureLongClickedListener;
-
 	private boolean mIndicateReceived = false;
 	private boolean mUseGreenBackground = false;
-
 	private OnQuoteListener onQuoteListener;
 
-	private final ListSelectionManager listSelectionManager = new ListSelectionManager();
-
-	private final AudioPlayer audioPlayer;
-
 	public MessageAdapter(XmppActivity activity, List<Message> messages) {
 		super(activity, 0, messages);
 		this.audioPlayer = new AudioPlayer(this);
@@ -149,6 +142,31 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 		updatePreferences();
 	}
 
+	public static boolean cancelPotentialWork(Message message, ImageView imageView) {
+		final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+
+		if (bitmapWorkerTask != null) {
+			final Message oldMessage = bitmapWorkerTask.message;
+			if (oldMessage == null || message != oldMessage) {
+				bitmapWorkerTask.cancel(true);
+			} else {
+				return false;
+			}
+		}
+		return true;
+	}
+
+	private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
+		if (imageView != null) {
+			final Drawable drawable = imageView.getDrawable();
+			if (drawable instanceof AsyncDrawable) {
+				final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
+				return asyncDrawable.getBitmapWorkerTask();
+			}
+		}
+		return null;
+	}
+
 	public void flagScreenOn() {
 		activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
 	}
@@ -164,7 +182,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 	public void setOnContactPictureLongClicked(
 			OnContactPictureLongClicked listener) {
 		this.mOnContactPictureLongClickedListener = listener;
-			}
+	}
 
 	public void setOnQuoteListener(OnQuoteListener listener) {
 		this.onQuoteListener = listener;
@@ -220,14 +238,14 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 			}
 		}
 		boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
-			&& message.getMergedStatus() <= Message.STATUS_RECEIVED;
+				&& message.getMergedStatus() <= Message.STATUS_RECEIVED;
 		if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || message.getTransferable() != null) {
 			FileParams params = message.getFileParams();
 			if (params.size > (1.5 * 1024 * 1024)) {
-				filesize = Math.round(params.size * 1f/ (1024 * 1024))+ " MiB";
+				filesize = Math.round(params.size * 1f / (1024 * 1024)) + " MiB";
 			} else if (params.size >= 1024) {
-				filesize = Math.round(params.size * 1f/ 1024) + " KiB";
-			} else if (params.size > 0){
+				filesize = Math.round(params.size * 1f / 1024) + " KiB";
+			} else if (params.size > 0) {
 				filesize = params.size + " B";
 			}
 			if (message.getTransferable() != null && message.getTransferable().getStatus() == Transferable.STATUS_FAILED) {
@@ -240,8 +258,8 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 				break;
 			case Message.STATUS_UNSEND:
 				Transferable d = message.getTransferable();
-				if (d!=null) {
-					info = getContext().getString(R.string.sending_file,d.getProgress());
+				if (d != null) {
+					info = getContext().getString(R.string.sending_file, d.getProgress());
 				} else {
 					info = getContext().getString(R.string.sending);
 				}
@@ -272,7 +290,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 		if (error && type == SENT) {
 			viewHolder.time.setTextColor(activity.getWarningTextColor());
 		} else {
-			viewHolder.time.setTextColor(this.getMessageTextColor(darkBackground,false));
+			viewHolder.time.setTextColor(this.getMessageTextColor(darkBackground, false));
 		}
 		if (message.getEncryption() == Message.ENCRYPTION_NONE) {
 			viewHolder.indicator.setVisibility(View.GONE);
@@ -302,7 +320,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 		String formatedTime = UIHelper.readableTimeDifferenceFull(getContext(), message.getMergedTimeSent());
 		if (message.getStatus() <= Message.STATUS_RECEIVED) {
 			if ((filesize != null) && (info != null)) {
-				viewHolder.time.setText(formatedTime + " \u00B7 " + filesize +" \u00B7 " + info);
+				viewHolder.time.setText(formatedTime + " \u00B7 " + filesize + " \u00B7 " + info);
 			} else if ((filesize == null) && (info != null)) {
 				viewHolder.time.setText(formatedTime + " \u00B7 " + info);
 			} else if ((filesize != null) && (info == null)) {
@@ -392,8 +410,8 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 			char current = body.length() > i ? body.charAt(i) : '\n';
 			if (lineStart == -1) {
 				if (previous == '\n') {
-					if ((current == '>' && UIHelper.isPositionFollowedByQuoteableCharacter(body,i))
-							|| current == '\u00bb' && !UIHelper.isPositionFollowedByQuote(body,i)) {
+					if ((current == '>' && UIHelper.isPositionFollowedByQuoteableCharacter(body, i))
+							|| current == '\u00bb' && !UIHelper.isPositionFollowedByQuote(body, i)) {
 						// Line start with quote
 						lineStart = i;
 						if (quoteStart == -1) quoteStart = i;
@@ -497,12 +515,12 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 			if (message.getConversation().getMode() == Conversation.MODE_MULTI && message.getStatus() == Message.STATUS_RECEIVED) {
 				Pattern pattern = NotificationService.generateNickHighlightPattern(message.getConversation().getMucOptions().getActualNick());
 				Matcher matcher = pattern.matcher(body);
-				while(matcher.find()) {
+				while (matcher.find()) {
 					body.setSpan(new StyleSpan(Typeface.BOLD), matcher.start(), matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
 				}
 			}
 			Matcher matcher = Emoticons.generatePattern(body).matcher(body);
-			while(matcher.find()) {
+			while (matcher.find()) {
 				if (matcher.start() < matcher.end()) {
 					body.setSpan(new RelativeSizeSpan(1.2f), matcher.start(), matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
 				}
@@ -531,29 +549,16 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 		viewHolder.audioPlayer.setVisibility(View.GONE);
 		viewHolder.download_button.setVisibility(View.VISIBLE);
 		viewHolder.download_button.setText(text);
-		viewHolder.download_button.setOnClickListener(new OnClickListener() {
-
-			@Override
-			public void onClick(View v) {
-				//TODO make proper reference to fragment
-				//activity.startDownloadable(message);
-			}
-		});
+		viewHolder.download_button.setOnClickListener(v -> ConversationFragment.downloadFile(activity, message));
 	}
 
-	private void displayOpenableMessage(ViewHolder viewHolder,final Message message) {
+	private void displayOpenableMessage(ViewHolder viewHolder, final Message message) {
 		viewHolder.image.setVisibility(View.GONE);
 		viewHolder.messageBody.setVisibility(View.GONE);
 		viewHolder.audioPlayer.setVisibility(View.GONE);
 		viewHolder.download_button.setVisibility(View.VISIBLE);
 		viewHolder.download_button.setText(activity.getString(R.string.open_x_file, UIHelper.getFileDescriptionString(activity, message)));
-		viewHolder.download_button.setOnClickListener(new OnClickListener() {
-
-			@Override
-			public void onClick(View v) {
-				openDownloadable(message);
-			}
-		});
+		viewHolder.download_button.setOnClickListener(v -> openDownloadable(message));
 	}
 
 	private void displayLocationMessage(ViewHolder viewHolder, final Message message) {
@@ -562,13 +567,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 		viewHolder.audioPlayer.setVisibility(View.GONE);
 		viewHolder.download_button.setVisibility(View.VISIBLE);
 		viewHolder.download_button.setText(R.string.show_location);
-		viewHolder.download_button.setOnClickListener(new OnClickListener() {
-
-			@Override
-			public void onClick(View v) {
-				showLocation(message);
-			}
-		});
+		viewHolder.download_button.setOnClickListener(v -> showLocation(message));
 	}
 
 	private void displayAudioMessage(ViewHolder viewHolder, Message message, boolean darkBackground) {
@@ -581,7 +580,6 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 		this.audioPlayer.init(audioPlayer, message);
 	}
 
-
 	private void displayImageMessage(ViewHolder viewHolder, final Message message) {
 		viewHolder.download_button.setVisibility(View.GONE);
 		viewHolder.messageBody.setVisibility(View.GONE);
@@ -594,7 +592,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 		if (Math.max(params.height, params.width) * metrics.density <= target) {
 			scaledW = (int) (params.width * metrics.density);
 			scaledH = (int) (params.height * metrics.density);
-		} else if (Math.max(params.height,params.width) <= target) {
+		} else if (Math.max(params.height, params.width) <= target) {
 			scaledW = params.width;
 			scaledH = params.height;
 		} else if (params.width <= params.height) {
@@ -608,17 +606,11 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 		layoutParams.setMargins(0, (int) (metrics.density * 4), 0, (int) (metrics.density * 4));
 		viewHolder.image.setLayoutParams(layoutParams);
 		activity.loadBitmap(message, viewHolder.image);
-		viewHolder.image.setOnClickListener(new OnClickListener() {
-
-			@Override
-			public void onClick(View v) {
-				openDownloadable(message);
-			}
-		});
+		viewHolder.image.setOnClickListener(v -> openDownloadable(message));
 	}
 
 	private void loadMoreMessages(Conversation conversation) {
-		conversation.setLastClearHistory(0,null);
+		conversation.setLastClearHistory(0, null);
 		activity.xmppConnectionService.updateConversation(conversation);
 		conversation.setHasMessagesLeftOnServer(true);
 		conversation.setFirstMamReference(null);
@@ -631,7 +623,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 		if (query != null) {
 			Toast.makeText(activity, R.string.fetching_history_from_server, Toast.LENGTH_LONG).show();
 		} else {
-			Toast.makeText(activity,R.string.not_fetching_history_retention_period, Toast.LENGTH_SHORT).show();
+			Toast.makeText(activity, R.string.not_fetching_history_retention_period, Toast.LENGTH_SHORT).show();
 		}
 	}
 
@@ -709,7 +701,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 			} else if (UIHelper.yesterday(message.getTimeSent())) {
 				viewHolder.status_message.setText(R.string.yesterday);
 			} else {
-				viewHolder.status_message.setText(DateUtils.formatDateTime(activity,message.getTimeSent(),DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR));
+				viewHolder.status_message.setText(DateUtils.formatDateTime(activity, message.getTimeSent(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR));
 			}
 			viewHolder.message_box.setBackgroundResource(activity.isDarkTheme() ? R.drawable.date_bubble_grey : R.drawable.date_bubble_white);
 			viewHolder.status_message.setTextColor(activity.getSecondaryTextColor());
@@ -719,12 +711,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 				viewHolder.status_message.setVisibility(View.GONE);
 				viewHolder.contact_picture.setVisibility(View.GONE);
 				viewHolder.load_more_messages.setVisibility(View.VISIBLE);
-				viewHolder.load_more_messages.setOnClickListener(new OnClickListener() {
-					@Override
-					public void onClick(View v) {
-						loadMoreMessages(message.getConversation());
-					}
-				});
+				viewHolder.load_more_messages.setOnClickListener(v -> loadMoreMessages(message.getConversation()));
 			} else {
 				viewHolder.status_message.setVisibility(View.VISIBLE);
 				viewHolder.load_more_messages.setVisibility(View.GONE);
@@ -732,10 +719,10 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 				boolean showAvatar;
 				if (conversation.getMode() == Conversation.MODE_SINGLE) {
 					showAvatar = true;
-					loadAvatar(message,viewHolder.contact_picture,activity.getPixel(32));
+					loadAvatar(message, viewHolder.contact_picture, activity.getPixel(32));
 				} else if (message.getCounterpart() != null || message.getTrueCounterpart() != null || (message.getCounterparts() != null && message.getCounterparts().size() > 0)) {
 					showAvatar = true;
-					loadAvatar(message,viewHolder.contact_picture,activity.getPixel(32));
+					loadAvatar(message, viewHolder.contact_picture, activity.getPixel(32));
 				} else {
 					showAvatar = false;
 				}
@@ -748,44 +735,34 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 			}
 			return view;
 		} else {
-			loadAvatar(message, viewHolder.contact_picture,activity.getPixel(48));
+			loadAvatar(message, viewHolder.contact_picture, activity.getPixel(48));
 		}
 
-		viewHolder.contact_picture
-			.setOnClickListener(new OnClickListener() {
-
-				@Override
-				public void onClick(View v) {
-					if (MessageAdapter.this.mOnContactPictureClickedListener != null) {
-						MessageAdapter.this.mOnContactPictureClickedListener
-								.onContactPictureClicked(message);
-					}
+		viewHolder.contact_picture.setOnClickListener(v -> {
+			if (MessageAdapter.this.mOnContactPictureClickedListener != null) {
+				MessageAdapter.this.mOnContactPictureClickedListener
+						.onContactPictureClicked(message);
+			}
 
-				}
-			});
-		viewHolder.contact_picture
-			.setOnLongClickListener(new OnLongClickListener() {
-
-				@Override
-				public boolean onLongClick(View v) {
-					if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) {
-						MessageAdapter.this.mOnContactPictureLongClickedListener
-								.onContactPictureLongClicked(message);
-						return true;
-					} else {
-						return false;
-					}
-				}
-			});
+		});
+		viewHolder.contact_picture.setOnLongClickListener(v -> {
+			if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) {
+				MessageAdapter.this.mOnContactPictureLongClickedListener
+						.onContactPictureLongClicked(message);
+				return true;
+			} else {
+				return false;
+			}
+		});
 
 		final Transferable transferable = message.getTransferable();
 		if (transferable != null && transferable.getStatus() != Transferable.STATUS_UPLOADING) {
 			if (transferable.getStatus() == Transferable.STATUS_OFFER) {
-				displayDownloadableMessage(viewHolder,message,activity.getString(R.string.download_x_file, UIHelper.getFileDescriptionString(activity, message)));
+				displayDownloadableMessage(viewHolder, message, activity.getString(R.string.download_x_file, UIHelper.getFileDescriptionString(activity, message)));
 			} else if (transferable.getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) {
 				displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message)));
 			} else {
-				displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first,darkBackground);
+				displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first, darkBackground);
 			}
 		} else if (message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
 			displayImageMessage(viewHolder, message);
@@ -805,20 +782,20 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 					displayInfoMessage(viewHolder, activity.getString(R.string.pgp_message), darkBackground);
 				}
 			} else {
-				displayInfoMessage(viewHolder,activity.getString(R.string.install_openkeychain),darkBackground);
+				displayInfoMessage(viewHolder, activity.getString(R.string.install_openkeychain), darkBackground);
 				viewHolder.message_box.setOnClickListener(new OnClickListener() {
 
-							@Override
-							public void onClick(View v) {
-								activity.showInstallPgpDialog();
-							}
-						});
+					@Override
+					public void onClick(View v) {
+						activity.showInstallPgpDialog();
+					}
+				});
 			}
 		} else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
-			displayDecryptionFailed(viewHolder,darkBackground);
+			displayDecryptionFailed(viewHolder, darkBackground);
 		} else {
 			if (message.isGeoUri()) {
-				displayLocationMessage(viewHolder,message);
+				displayLocationMessage(viewHolder, message);
 			} else if (message.bodyIsOnlyEmojis() && message.getType() != Message.TYPE_PRIVATE) {
 				displayEmojiMessage(viewHolder, message.getBody().trim());
 			} else if (message.treatAsDownloadable()) {
@@ -841,7 +818,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 		}
 
 		if (type == RECEIVED) {
-			if(isInValidSession) {
+			if (isInValidSession) {
 				int bubble;
 				if (!mUseGreenBackground) {
 					bubble = activity.getThemeResource(R.attr.message_bubble_received_monochrome, R.drawable.message_bubble_received_white);
@@ -912,59 +889,10 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 		audioPlayer.stop();
 	}
 
-	public interface OnQuoteListener {
-		public void onQuote(String text);
-	}
-
-	private class MessageBodyActionModeCallback implements ActionMode.Callback {
-
-		private final TextView textView;
-
-		public MessageBodyActionModeCallback(TextView textView) {
-			this.textView = textView;
-		}
-
-		@Override
-		public boolean onCreateActionMode(ActionMode mode, Menu menu) {
-			if (onQuoteListener != null) {
-				int quoteResId = activity.getThemeResource(R.attr.icon_quote, R.drawable.ic_action_reply);
-				// 3rd item is placed after "copy" item
-				menu.add(0, android.R.id.button1, 3, R.string.quote).setIcon(quoteResId)
-						.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
-			}
-			return false;
-		}
-
-		@Override
-		public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
-			return false;
-		}
-
-		@Override
-		public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
-			if (item.getItemId() == android.R.id.button1) {
-				int start = textView.getSelectionStart();
-				int end = textView.getSelectionEnd();
-				if (end > start) {
-					String text = transformText(textView.getText(), start, end, false);
-					if (onQuoteListener != null) {
-						onQuoteListener.onQuote(text);
-					}
-					mode.finish();
-				}
-				return true;
-			}
-			return false;
-		}
-
-		@Override
-		public void onDestroyActionMode(ActionMode mode) {}
-	}
-
 	public void openDownloadable(Message message) {
 		DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
 		if (!file.exists()) {
-			Toast.makeText(activity,R.string.file_deleted,Toast.LENGTH_SHORT).show();
+			Toast.makeText(activity, R.string.file_deleted, Toast.LENGTH_SHORT).show();
 			return;
 		}
 		Intent openIntent = new Intent(Intent.ACTION_VIEW);
@@ -976,7 +904,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 		try {
 			uri = FileBackend.getUriForFile(activity, file);
 		} catch (SecurityException e) {
-			Log.d(Config.LOGTAG,"No permission to access "+file.getAbsolutePath(),e);
+			Log.d(Config.LOGTAG, "No permission to access " + file.getAbsolutePath(), e);
 			Toast.makeText(activity, activity.getString(R.string.no_permission_to_access_x, file.getAbsolutePath()), Toast.LENGTH_SHORT).show();
 			return;
 		}
@@ -985,23 +913,23 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 		PackageManager manager = activity.getPackageManager();
 		List<ResolveInfo> info = manager.queryIntentActivities(openIntent, 0);
 		if (info.size() == 0) {
-			openIntent.setDataAndType(uri,"*/*");
+			openIntent.setDataAndType(uri, "*/*");
 		}
 		try {
 			getContext().startActivity(openIntent);
-		}  catch (ActivityNotFoundException e) {
-			Toast.makeText(activity,R.string.no_application_found_to_open_file,Toast.LENGTH_SHORT).show();
+		} catch (ActivityNotFoundException e) {
+			Toast.makeText(activity, R.string.no_application_found_to_open_file, Toast.LENGTH_SHORT).show();
 		}
 	}
 
 	public void showLocation(Message message) {
-		for(Intent intent : GeoHelper.createGeoIntentsFromMessage(message)) {
+		for (Intent intent : GeoHelper.createGeoIntentsFromMessage(message)) {
 			if (intent.resolveActivity(getContext().getPackageManager()) != null) {
 				getContext().startActivity(intent);
 				return;
 			}
 		}
-		Toast.makeText(activity,R.string.no_application_found_to_display_location,Toast.LENGTH_SHORT).show();
+		Toast.makeText(activity, R.string.no_application_found_to_display_location, Toast.LENGTH_SHORT).show();
 	}
 
 	public void updatePreferences() {
@@ -1010,13 +938,35 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 		this.mUseGreenBackground = p.getBoolean("use_green_background", activity.getResources().getBoolean(R.bool.use_green_background));
 	}
 
-	public TextView getMessageBody(View view) {
-		final Object tag = view.getTag();
-		if (tag instanceof ViewHolder) {
-			final ViewHolder viewHolder = (ViewHolder) tag;
-			return viewHolder.messageBody;
+	public void loadAvatar(Message message, ImageView imageView, int size) {
+		if (cancelPotentialWork(message, imageView)) {
+			final Bitmap bm = activity.avatarService().get(message, size, true);
+			if (bm != null) {
+				cancelPotentialWork(message, imageView);
+				imageView.setImageBitmap(bm);
+				imageView.setBackgroundColor(Color.TRANSPARENT);
+			} else {
+				@ColorInt int bg;
+				if (message.getType() == Message.TYPE_STATUS && message.getCounterparts() != null && message.getCounterparts().size() > 1) {
+					bg = Color.TRANSPARENT;
+				} else {
+					bg = UIHelper.getColorForName(UIHelper.getMessageDisplayName(message));
+				}
+				imageView.setBackgroundColor(bg);
+				imageView.setImageDrawable(null);
+				final BitmapWorkerTask task = new BitmapWorkerTask(imageView, size);
+				final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task);
+				imageView.setImageDrawable(asyncDrawable);
+				try {
+					task.execute(message);
+				} catch (final RejectedExecutionException ignored) {
+				}
+			}
 		}
-		return null;
+	}
+
+	public interface OnQuoteListener {
+		void onQuote(String text);
 	}
 
 	public interface OnContactPictureClicked {
@@ -1029,6 +979,9 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 
 	private static class ViewHolder {
 
+		public Button load_more_messages;
+		public ImageView edit_indicator;
+		public RelativeLayout audioPlayer;
 		protected LinearLayout message_box;
 		protected Button download_button;
 		protected ImageView image;
@@ -1039,15 +992,71 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 		protected ImageView contact_picture;
 		protected TextView status_message;
 		protected TextView encryption;
-		public Button load_more_messages;
-		public ImageView edit_indicator;
-		public RelativeLayout audioPlayer;
+	}
+
+	static class AsyncDrawable extends BitmapDrawable {
+		private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
+
+		public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
+			super(res, bitmap);
+			bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
+		}
+
+		public BitmapWorkerTask getBitmapWorkerTask() {
+			return bitmapWorkerTaskReference.get();
+		}
+	}
+
+	private class MessageBodyActionModeCallback implements ActionMode.Callback {
+
+		private final TextView textView;
+
+		public MessageBodyActionModeCallback(TextView textView) {
+			this.textView = textView;
+		}
+
+		@Override
+		public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+			if (onQuoteListener != null) {
+				int quoteResId = activity.getThemeResource(R.attr.icon_quote, R.drawable.ic_action_reply);
+				// 3rd item is placed after "copy" item
+				menu.add(0, android.R.id.button1, 3, R.string.quote).setIcon(quoteResId)
+						.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+			}
+			return false;
+		}
+
+		@Override
+		public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+			return false;
+		}
+
+		@Override
+		public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+			if (item.getItemId() == android.R.id.button1) {
+				int start = textView.getSelectionStart();
+				int end = textView.getSelectionEnd();
+				if (end > start) {
+					String text = transformText(textView.getText(), start, end, false);
+					if (onQuoteListener != null) {
+						onQuoteListener.onQuote(text);
+					}
+					mode.finish();
+				}
+				return true;
+			}
+			return false;
+		}
+
+		@Override
+		public void onDestroyActionMode(ActionMode mode) {
+		}
 	}
 
 	class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
 		private final WeakReference<ImageView> imageViewReference;
-		private Message message = null;
 		private final int size;
+		private Message message = null;
 
 		public BitmapWorkerTask(ImageView imageView, int size) {
 			imageViewReference = new WeakReference<>(imageView);
@@ -1071,69 +1080,4 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 			}
 		}
 	}
-
-	public void loadAvatar(Message message, ImageView imageView, int size) {
-		if (cancelPotentialWork(message, imageView)) {
-			final Bitmap bm = activity.avatarService().get(message, size, true);
-			if (bm != null) {
-				cancelPotentialWork(message, imageView);
-				imageView.setImageBitmap(bm);
-				imageView.setBackgroundColor(Color.TRANSPARENT);
-			} else {
-				@ColorInt int bg;
-				if (message.getType() == Message.TYPE_STATUS && message.getCounterparts() != null && message.getCounterparts().size() > 1) {
-					bg = Color.TRANSPARENT;
-				} else {
-					bg = UIHelper.getColorForName(UIHelper.getMessageDisplayName(message));
-				}
-				imageView.setBackgroundColor(bg);
-				imageView.setImageDrawable(null);
-				final BitmapWorkerTask task = new BitmapWorkerTask(imageView, size);
-				final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task);
-				imageView.setImageDrawable(asyncDrawable);
-				try {
-					task.execute(message);
-				} catch (final RejectedExecutionException ignored) {
-				}
-			}
-		}
-	}
-
-	public static boolean cancelPotentialWork(Message message, ImageView imageView) {
-		final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
-
-		if (bitmapWorkerTask != null) {
-			final Message oldMessage = bitmapWorkerTask.message;
-			if (oldMessage == null || message != oldMessage) {
-				bitmapWorkerTask.cancel(true);
-			} else {
-				return false;
-			}
-		}
-		return true;
-	}
-
-	private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
-		if (imageView != null) {
-			final Drawable drawable = imageView.getDrawable();
-			if (drawable instanceof AsyncDrawable) {
-				final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
-				return asyncDrawable.getBitmapWorkerTask();
-			}
-		}
-		return null;
-	}
-
-	static class AsyncDrawable extends BitmapDrawable {
-		private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
-
-		public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
-			super(res, bitmap);
-			bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
-		}
-
-		public BitmapWorkerTask getBitmapWorkerTask() {
-			return bitmapWorkerTaskReference.get();
-		}
-	}
 }