package de.thedevstack.conversationsplus.ui.adapter; import android.content.ActivityNotFoundException; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Typeface; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; import android.text.Spannable; import android.text.SpannableString; import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; import android.text.util.Linkify; import android.util.DisplayMetrics; import android.util.Patterns; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import java.lang.ref.WeakReference; import java.util.List; import java.util.concurrent.RejectedExecutionException; import java.util.regex.Matcher; import java.util.regex.Pattern; import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.ConversationsPlusColors; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.crypto.axolotl.XmppAxolotlSession; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Conversation; import de.thedevstack.conversationsplus.entities.DownloadableFile; import de.thedevstack.conversationsplus.entities.FileParams; import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.entities.Transferable; import de.thedevstack.conversationsplus.enums.FileStatus; import de.thedevstack.conversationsplus.persistance.FileBackend; import de.thedevstack.conversationsplus.providers.ConversationsPlusFileProvider; import de.thedevstack.conversationsplus.services.AvatarService; import de.thedevstack.conversationsplus.services.filetransfer.http.download.AutomaticFileDownload; import de.thedevstack.conversationsplus.ui.ConversationActivity; import de.thedevstack.conversationsplus.utils.CryptoHelper; import de.thedevstack.conversationsplus.utils.GeoHelper; import de.thedevstack.conversationsplus.utils.ImageUtil; import de.thedevstack.conversationsplus.utils.MessageUtil; import de.thedevstack.conversationsplus.utils.UIHelper; import de.thedevstack.conversationsplus.utils.ui.TextViewUtil; import de.thedevstack.conversationsplus.utils.ui.ViewUtil; public class MessageAdapter extends ArrayAdapter { private static final int SENT = 0; private static final int RECEIVED = 1; private static final int STATUS = 2; private static final int ME_COMMAND = 3; private static final Pattern XMPP_PATTERN = Pattern .compile("xmpp\\:(?:(?:[" + Patterns.GOOD_IRI_CHAR + "\\;\\/\\?\\@\\&\\=\\#\\~\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])" + "|(?:\\%[a-fA-F0-9]{2}))+"); private ConversationActivity activity; private DisplayMetrics metrics; private OnContactPictureClicked mOnContactPictureClickedListener; private OnContactPictureLongClicked mOnContactPictureLongClickedListener; private OnLongClickListener openContextMenu = new OnLongClickListener() { @Override public boolean onLongClick(View v) { v.showContextMenu(); return true; } }; public MessageAdapter(ConversationActivity activity, List messages) { super(activity, 0, messages); this.activity = activity; metrics = getContext().getResources().getDisplayMetrics(); } public void setOnContactPictureClicked(OnContactPictureClicked listener) { this.mOnContactPictureClickedListener = listener; } public void setOnContactPictureLongClicked( OnContactPictureLongClicked listener) { this.mOnContactPictureLongClickedListener = listener; } @Override public int getViewTypeCount() { return 4; } public int getItemViewType(Message message) { if (message.hasMeCommand()) { return ME_COMMAND; } else if (message.getType() == Message.TYPE_STATUS) { return STATUS; } else if (message.getStatus() <= Message.STATUS_RECEIVED) { return RECEIVED; } return SENT; } @Override public int getItemViewType(int position) { return this.getItemViewType(getItem(position)); } private int getMessageTextColor(boolean onDark, boolean primary) { if (onDark) { return primary ? ConversationsPlusColors.primaryTextOnDark() : ConversationsPlusColors.secondaryTextOnDark(); } else { return primary ? ConversationsPlusColors.primaryText() : ConversationsPlusColors.secondaryText(); } } private void displayStatus(ViewHolder viewHolder, Message message, int type) { String filesize = null; String info = null; boolean error = false; if (viewHolder.indicatorReceived != null) { viewHolder.indicatorReceived.setVisibility(View.GONE); } boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI && message.getStatus() <= Message.STATUS_RECEIVED; if (message.hasFileAttached() || message.getTransferable() != null) { FileParams params = message.getFileParams(); if (null != params) { filesize = UIHelper.getHumanReadableFileSize(params.getSize()); } if (message.getTransferable() != null && message.getTransferable().getStatus() == Transferable.STATUS_FAILED) { error = true; } } switch (message.getStatus()) { case Message.STATUS_WAITING: info = getContext().getString(R.string.waiting); break; case Message.STATUS_UNSEND: Transferable d = message.getTransferable(); if (d != null) { info = getContext().getString(R.string.sending_file, d.getProgress()); } else { info = getContext().getString(R.string.sending); } break; case Message.STATUS_OFFERED: info = getContext().getString(R.string.offering); break; case Message.STATUS_SEND_RECEIVED: if (ConversationsPlusPreferences.indicateReceived()) { viewHolder.indicatorReceived.setVisibility(View.VISIBLE); } break; case Message.STATUS_SEND_DISPLAYED: if (ConversationsPlusPreferences.indicateReceived()) { viewHolder.indicatorReceived.setVisibility(View.VISIBLE); } break; case Message.STATUS_SEND_FAILED: info = getContext().getString(R.string.send_failed); error = true; break; default: if (multiReceived) { info = UIHelper.getMessageDisplayName(message); } break; } if (error && type == SENT) { viewHolder.time.setTextColor(ConversationsPlusColors.warning()); } else { viewHolder.time.setTextColor(this.getMessageTextColor(viewHolder.darkBackground, false)); } if (message.getEncryption() == Message.ENCRYPTION_NONE) { viewHolder.indicator.setVisibility(View.GONE); } else { viewHolder.indicator.setImageResource(viewHolder.darkBackground ? R.drawable.ic_lock_white_18dp : R.drawable.ic_lock_black_18dp); viewHolder.indicator.setVisibility(View.VISIBLE); if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { XmppAxolotlSession.Trust trust = message.getConversation() .getAccount().getAxolotlService().getFingerprintTrust( message.getFingerprint()); if (trust == null || (!trust.trusted() && !trust.trustedInactive())) { viewHolder.indicator.setColorFilter(ConversationsPlusColors.warning()); viewHolder.indicator.setAlpha(1.0f); } else { viewHolder.indicator.clearColorFilter(); if (viewHolder.darkBackground) { viewHolder.indicator.setAlpha(0.7f); } else { viewHolder.indicator.setAlpha(0.57f); } } } else { viewHolder.indicator.clearColorFilter(); if (viewHolder.darkBackground) { viewHolder.indicator.setAlpha(0.7f); } else { viewHolder.indicator.setAlpha(0.57f); } } } String formatedTime = UIHelper.readableTimeDifferenceFull(getContext(), message.getTimeSent()); if (message.getStatus() <= Message.STATUS_RECEIVED) { StringBuilder timeText = new StringBuilder(); timeText.append((null != formatedTime) ? formatedTime + ((null != info || null != filesize) ? " \u00B7 " : "") : ""); timeText.append((null != filesize) ? filesize + ((null != info) ? " \u00B7 " : "") : ""); timeText.append((null != info) ? info : ""); viewHolder.time.setText(timeText); } else { if ((filesize != null) && (info != null)) { viewHolder.time.setText(filesize + " \u00B7 " + info); } else if ((filesize == null) && (info != null)) { if (error) { viewHolder.time.setText(info + " \u00B7 " + formatedTime); } else { viewHolder.time.setText(info); } } else if ((filesize != null) && (info == null)) { viewHolder.time.setText(filesize + " \u00B7 " + formatedTime); } else { viewHolder.time.setText(formatedTime); } } if (message.hasFileAttached() && null != message.getFileParams() && null != viewHolder.remoteFileStatus) { FileStatus fileStatus = message.getFileParams().getFileStatus(); if (fileStatus == FileStatus.DELETE_FAILED || fileStatus == FileStatus.DELETED || fileStatus == FileStatus.DELETING || fileStatus == FileStatus.NOT_FOUND) { viewHolder.remoteFileStatus.setVisibility(View.VISIBLE); switch (fileStatus) { case DELETE_FAILED: TextViewUtil.setColor(viewHolder.remoteFileStatus, R.color.error); viewHolder.remoteFileStatus.setText(R.string.remote_filestatus_delete_failed); break; case DELETED: viewHolder.remoteFileStatus.setText(R.string.remote_filestatus_delete_success); break; case DELETING: viewHolder.remoteFileStatus.setText(R.string.remote_filestatus_delete_inprogress); break; case NOT_FOUND: TextViewUtil.setColor(viewHolder.remoteFileStatus, R.color.error); viewHolder.remoteFileStatus.setText(R.string.remote_filestatus_not_found); break; } } } } private void displayInfoMessage(ViewHolder viewHolder, String text) { if (viewHolder.download_button != null) { viewHolder.download_button.setVisibility(View.GONE); } if (null != viewHolder.image) { viewHolder.image.setVisibility(View.GONE); } viewHolder.messageBody.setVisibility(View.VISIBLE); viewHolder.messageBody.setText(text); viewHolder.messageBody.setTextColor(getMessageTextColor(viewHolder.darkBackground, false)); viewHolder.messageBody.setTypeface(null, Typeface.ITALIC); viewHolder.messageBody.setTextIsSelectable(false); } private void displayDecryptionFailed(ViewHolder viewHolder) { if (viewHolder.download_button != null) { viewHolder.download_button.setVisibility(View.GONE); } viewHolder.image.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.VISIBLE); viewHolder.messageBody.setText(getContext().getString( R.string.decryption_failed)); viewHolder.messageBody.setTextColor(getMessageTextColor(viewHolder.darkBackground, false)); viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); viewHolder.messageBody.setTextIsSelectable(false); } private void displayTextMessage(final ViewHolder viewHolder, final Message message) { if (viewHolder.download_button != null) { viewHolder.download_button.setVisibility(View.GONE); } if (null != viewHolder.image) { viewHolder.image.setVisibility(View.GONE); } viewHolder.messageBody.setVisibility(View.VISIBLE); viewHolder.messageBody.setIncludeFontPadding(true); if (message.getBody() != null) { final String nick = UIHelper.getMessageDisplayName(message); String body; if (message.hasMeCommand()) { body = message.getBodyReplacedMeCommand(nick); } else { body = message.getBody(); } final SpannableString formattedBody = new SpannableString(body); if (message.getType() != Message.TYPE_PRIVATE) { if (message.hasMeCommand()) { final Spannable span = new SpannableString(formattedBody); span.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, nick.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); viewHolder.messageBody.setText(span); } else { viewHolder.messageBody.setText(formattedBody); } } else { String privateMarker; if (message.getStatus() <= Message.STATUS_RECEIVED) { privateMarker = activity.getString(R.string.private_message); } else { final String to; if (message.getCounterpart() != null) { to = message.getCounterpart().getResourcepart(); } else { to = ""; } privateMarker = activity.getString(R.string.private_message_to, to); } final Spannable span = new SpannableString(privateMarker + " " + formattedBody); span.setSpan(new ForegroundColorSpan(getMessageTextColor(viewHolder.darkBackground, false)), 0, privateMarker .length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); span.setSpan(new StyleSpan(Typeface.BOLD), 0, privateMarker.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); if (message.hasMeCommand()) { span.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), privateMarker.length() + 1, privateMarker.length() + 1 + nick.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } viewHolder.messageBody.setText(span); } int patternMatchCount = 0; int oldAutoLinkMask = viewHolder.messageBody.getAutoLinkMask(); // first check if we have a match on XMPP_PATTERN so we do not have to check for EMAIL_ADDRESSES patternMatchCount += countMatches(XMPP_PATTERN, body); if ((Linkify.EMAIL_ADDRESSES & oldAutoLinkMask) != 0 && patternMatchCount > 0) { oldAutoLinkMask -= Linkify.EMAIL_ADDRESSES; } // count matches for all patterns if ((Linkify.WEB_URLS & oldAutoLinkMask) != 0) { patternMatchCount += countMatches(Patterns.WEB_URL, body); } if ((Linkify.EMAIL_ADDRESSES & oldAutoLinkMask) != 0) { patternMatchCount += countMatches(Patterns.EMAIL_ADDRESS, body); } if ((Linkify.PHONE_NUMBERS & oldAutoLinkMask) != 0) { patternMatchCount += countMatches(Patterns.PHONE, body); } viewHolder.messageBody.setTextIsSelectable(patternMatchCount <= 1); viewHolder.messageBody.setAutoLinkMask(0); Linkify.addLinks(viewHolder.messageBody, XMPP_PATTERN, "xmpp"); viewHolder.messageBody.setAutoLinkMask(oldAutoLinkMask); } else { viewHolder.messageBody.setText(""); viewHolder.messageBody.setTextIsSelectable(false); } viewHolder.messageBody.setTextColor(this.getMessageTextColor(viewHolder.darkBackground, true)); viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); viewHolder.messageBody.setOnLongClickListener(openContextMenu); } /** * Counts the number of occurrences of the pattern in body. * * @param pattern the pattern to match * @param body the body to find the pattern * @return the number of occurrences */ private int countMatches(Pattern pattern, String body) { Matcher matcher = pattern.matcher(body); int count = 0; while (matcher.find()) { count++; } return count; } private void displayDownloadButton(ViewHolder viewHolder, String btnText, OnClickListener onClickListener) { viewHolder.download_button.setVisibility(View.VISIBLE); viewHolder.download_button.setText(btnText); viewHolder.download_button.setOnClickListener(onClickListener); viewHolder.download_button.setOnLongClickListener(openContextMenu); } private void displayFileInfoForFileMessage(final Message message, ViewHolder viewHolder) { viewHolder.messageBody.setVisibility(View.VISIBLE); StringBuilder fileInfos = new StringBuilder(); String filename = UIHelper.getDisplayFilename(message); fileInfos.append((null != filename && !filename.isEmpty()) ? (filename) : ""); int oldAutoLinkMask = viewHolder.messageBody.getAutoLinkMask(); viewHolder.messageBody.setAutoLinkMask(0); viewHolder.messageBody.setText(fileInfos); viewHolder.messageBody.setAutoLinkMask(oldAutoLinkMask); } private void displayDownloadableMessage(ViewHolder viewHolder, final Message message) { viewHolder.image.setVisibility(View.GONE); FileParams fileParams = message.getFileParams(); String btnText; int resId = R.string.download_x_file; if ((message.getTransferable() != null && message.getTransferable().getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) || (null != fileParams && -1 == fileParams.getSize())) { resId = R.string.check_x_filesize; } if (null != fileParams) { this.displayFileInfoForFileMessage(message, viewHolder); btnText = activity.getString(resId, ""); } else { viewHolder.messageBody.setVisibility(View.GONE); btnText = activity.getString(resId, UIHelper.getFileDescriptionString(activity, message)); } this.displayDownloadButton(viewHolder, btnText, new OnClickListener() { @Override public void onClick(View v) { activity.startDownloadable(message); } }); } private void displayOpenableMessage(ViewHolder viewHolder, final Message message) { viewHolder.image.setVisibility(View.GONE); FileParams fileParams = message.getFileParams(); String btnText; if (null != fileParams) { this.displayFileInfoForFileMessage(message, viewHolder); btnText = activity.getString(R.string.cplus_open); } else { viewHolder.messageBody.setVisibility(View.GONE); btnText = activity.getString(R.string.open_x_file, UIHelper.getFileDescriptionString(activity, message)); } this.displayDownloadButton(viewHolder, btnText, new OnClickListener() { @Override public void onClick(View v) { openDownloadable(message); } }); } private void displayLocationMessage(ViewHolder viewHolder, final Message message) { viewHolder.image.setVisibility(View.GONE); viewHolder.messageBody.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.setOnLongClickListener(openContextMenu); } private void displayImageMessage(ViewHolder viewHolder, final Message message) { if (viewHolder.download_button != null) { viewHolder.download_button.setVisibility(View.GONE); } ImageUtil.loadBitmap(message, viewHolder.image, viewHolder.messageBody, true); viewHolder.image.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { openDownloadable(message); } }); viewHolder.image.setOnLongClickListener(openContextMenu); } private View displayStatusMessage(final Message message, ViewHolder viewHolder) { if (null != viewHolder) { final Conversation conversation = message.getConversation(); viewHolder.status_message.setVisibility(View.VISIBLE); viewHolder.contact_picture.setVisibility(View.VISIBLE); if (conversation.getMode() == Conversation.MODE_SINGLE) { viewHolder.contact_picture.setImageBitmap(AvatarService.getInstance().get(conversation.getContact(), activity.getPixel(32))); viewHolder.contact_picture.setAlpha(0.5f); } viewHolder.status_message.setText(message.getBody()); } return viewHolder.view; } private void displayFileMessage(final Message message, ViewHolder viewHolder) { Transferable transferable = message.getTransferable(); if (FileStatus.CHECKING_FILE_SIZE == message.getFileParams().getFileStatus()) { displayInfoMessage(viewHolder, activity.getString(R.string.checking_remote_filesize)); } else if (MessageUtil.isAttachedFileAnImage(message) && (FileStatus.DOWNLOADED == message.getFileParams().getFileStatus()) || FileStatus.DELETED == message.getFileParams().getFileStatus() || FileStatus.DELETING == message.getFileParams().getFileStatus() || FileStatus.DELETE_FAILED == message.getFileParams().getFileStatus() || FileStatus.UPLOADED == message.getFileParams().getFileStatus()) { displayImageMessage(viewHolder, message); } else if ((MessageUtil.isTypeFileAndDecrypted(message) || FileStatus.DOWNLOADED == message.getFileParams().getFileStatus()) && !MessageUtil.needsDownload(message)) { displayOpenableMessage(viewHolder, message); } else if (Message.Decision.NEVER == message.treatAsDownloadable() || !MessageUtil.mayFileRemoteAvailable(message)) { displayTextMessage(viewHolder, message); } else if (FileStatus.UPLOAD_FAILED == message.getFileParams().getFileStatus()) { displayImageMessage(viewHolder, message); // TODO Show failed status } else if (null != transferable) { switch (transferable.getStatus()) { case Transferable.STATUS_OFFER: case Transferable.STATUS_OFFER_CHECK_FILESIZE: displayDownloadableMessage(viewHolder, message); break; case Transferable.STATUS_UPLOADING: // Should not happen, since this is now covered by the other if-statements // TODO Maybe in Jingle File Transfer?? Needs to be checked! break; case Transferable.STATUS_DELETED: case Transferable.STATUS_CHECKING: case Transferable.STATUS_FAILED: case Transferable.STATUS_DOWNLOADING: case Transferable.STATUS_UNKNOWN: displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first); break; } } else { displayDownloadableMessage(viewHolder, message); } } private void loadAvatar(Message message, ImageView imageView) { if (cancelPotentialWork(message, imageView)) { int avatarPixel = imageView.getResources().getDimensionPixelOffset(R.dimen.msg_avatar_size); final Bitmap bm = AvatarService.getInstance().get(message, avatarPixel, true); if (bm != null) { imageView.setImageBitmap(bm); imageView.setBackgroundColor(0x00000000); } else { imageView.setBackgroundColor(UIHelper.getColorForName(UIHelper.getMessageDisplayName(message))); imageView.setImageDrawable(null); final BitmapWorkerTask task = new BitmapWorkerTask(imageView); final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task); imageView.setImageDrawable(asyncDrawable); try { task.execute(message); } catch (final RejectedExecutionException ignored) { } } } } private void displayAvatar(final Message message, int type, ViewHolder viewHolder) { if (type == ME_COMMAND || (type == RECEIVED)) { // && message.getConversation().getMode() == Conversation.MODE_MULTI ImageView imageView = viewHolder.contact_picture; if (null != imageView) { this.loadAvatar(message, imageView); imageView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (MessageAdapter.this.mOnContactPictureClickedListener != null) { MessageAdapter.this.mOnContactPictureClickedListener .onContactPictureClicked(message); } } }); if (message.getConversation().getMode() == Conversation.MODE_MULTI) { imageView.setOnLongClickListener(new OnLongClickListener() { @Override public boolean onLongClick(View v) { if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) { MessageAdapter.this.mOnContactPictureLongClickedListener .onContactPictureLongClicked(message); return true; } else { return false; } } }); } } } } private View initializeView(int type, ViewGroup parent) { Integer viewResId = null; switch (type) { case SENT: viewResId = R.layout.message_sent; break; case RECEIVED: viewResId = R.layout.message_received; break; case STATUS: viewResId = R.layout.message_status; break; case ME_COMMAND: viewResId = R.layout.message_mecmd; break; } return activity.getLayoutInflater().inflate(viewResId, parent, false); } private ViewHolder initializeViewHolderAndView(Message message, int type, ViewGroup parent) { View view = initializeView(type, parent); ViewHolder viewHolder = new ViewHolder(view); if (SENT == type || RECEIVED == type || ME_COMMAND == type) { viewHolder.message_box = ViewUtil.visible(view, R.id.message_box); viewHolder.indicator = (ImageView) view.findViewById(R.id.security_indicator); viewHolder.messageBody = (TextView) view.findViewById(R.id.message_body); viewHolder.time = (TextView) view.findViewById(R.id.message_time); viewHolder.indicatorReceived = (ImageView) view.findViewById(R.id.indicator_received); } if ((SENT == type || RECEIVED == type)) { viewHolder.download_button = (Button) view.findViewById(R.id.download_button); viewHolder.image = (ImageView) view.findViewById(R.id.message_image); } if (ME_COMMAND == type || (RECEIVED == type)) { // && message.getConversation().getMode() == Conversation.MODE_MULTI --> only muc received msgs viewHolder.contact_picture = ViewUtil.visible(view, R.id.message_photo); } if (RECEIVED == type) { viewHolder.encryption = (TextView) view.findViewById(R.id.message_encryption); } if (STATUS == type) { viewHolder.contact_picture = ViewUtil.visible(view, R.id.message_photo); viewHolder.status_message = TextViewUtil.visible(view, R.id.status_message); } if (SENT == type) { viewHolder.remoteFileStatus = TextViewUtil.gone(view, R.id.remote_file_status); } view.setTag(viewHolder); return viewHolder; } @Override public View getView(int position, View view, ViewGroup parent) { final Message message = getItem(position); final boolean isInValidSession = message.isValidInSession(); final Conversation conversation = message.getConversation(); final Account account = conversation.getAccount(); final int type = getItemViewType(position); ViewHolder viewHolder; if (null == view) { viewHolder = initializeViewHolderAndView(message, type, parent); view = viewHolder.view; } else { viewHolder = (ViewHolder) view.getTag(); if (null == viewHolder) { return view; } } if (type == STATUS) { return displayStatusMessage(message, viewHolder); } this.displayAvatar(message, type, viewHolder); viewHolder.darkBackground = (type == RECEIVED && !isInValidSession); this.displayStatus(viewHolder, message, type); if (null != message.getTransferable() || message.hasFileAttached()) { if (!(message.trusted() && MessageUtil.needsDownload(message) && ConversationsPlusPreferences.autoAcceptFileSize() > 0 && message.isHttpUploaded() || ConversationsPlusPreferences.autoDownloadFileLink())) { new AutomaticFileDownload(false).transferFile(message); } displayFileMessage(message, viewHolder); } else if (GeoHelper.isGeoUri(message.getBody())) { displayLocationMessage(viewHolder, message); } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { if (activity.hasPgp()) { if (account.getPgpDecryptionService().isRunning()) { displayInfoMessage(viewHolder, activity.getString(R.string.message_decrypting)); } else { displayInfoMessage(viewHolder, activity.getString(R.string.pgp_message)); } } else { displayInfoMessage(viewHolder, activity.getString(R.string.install_openkeychain)); if (viewHolder != null) { viewHolder.message_box.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { activity.showInstallPgpDialog(); } }); } } } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { displayDecryptionFailed(viewHolder); } else { displayTextMessage(viewHolder, message); } if (type == RECEIVED) { if (isInValidSession) { viewHolder.encryption.setVisibility(View.GONE); } else { viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received_warning); viewHolder.encryption.setVisibility(View.VISIBLE); viewHolder.encryption.setText(CryptoHelper.encryptionTypeToText(message.getEncryption())); } } return view; } public void openDownloadable(Message message) { DownloadableFile file = FileBackend.getFile(message); if (!file.exists()) { Toast.makeText(activity, R.string.file_deleted, Toast.LENGTH_SHORT).show(); return; } boolean bInPrivateStorage = false; if (file.getAbsolutePath().startsWith(FileBackend.getPrivateFileDirectoryPath())) { bInPrivateStorage = true; } Intent openIntent = new Intent(Intent.ACTION_VIEW); String mime = file.getMimeType(); if (mime == null) { mime = "*/*"; } Uri uri; if (bInPrivateStorage) { uri = ConversationsPlusFileProvider.createUriForPrivateFile(file); } else { uri = Uri.fromFile(file); } openIntent.setDataAndType(uri, mime); PackageManager manager = activity.getPackageManager(); List infos = manager.queryIntentActivities(openIntent, 0); if (bInPrivateStorage) { for (ResolveInfo info : infos) { ConversationsPlusApplication.getAppContext().grantUriPermission(info.activityInfo.packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); } } if (infos.size() == 0) { openIntent.setDataAndType(uri, "*/*"); } if (bInPrivateStorage) { openIntent.putExtra(Intent.EXTRA_STREAM, uri); openIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); } try { getContext().startActivity(openIntent); return; } catch (ActivityNotFoundException e) { //ignored } 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)) { 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(); } public interface OnContactPictureClicked { void onContactPictureClicked(Message message); } public interface OnContactPictureLongClicked { void onContactPictureLongClicked(Message message); } private static class ViewHolder { protected ViewHolder(View view) { this.view = view; } protected View view; protected LinearLayout message_box; protected Button download_button; protected ImageView image; protected ImageView indicator; protected ImageView indicatorReceived; protected TextView time; protected TextView messageBody; protected ImageView contact_picture; protected TextView status_message; protected TextView encryption; public TextView remoteFileStatus; protected boolean darkBackground; } class BitmapWorkerTask extends AsyncTask { private final WeakReference imageViewReference; private Message message = null; public BitmapWorkerTask(ImageView imageView) { imageViewReference = new WeakReference<>(imageView); } @Override protected Bitmap doInBackground(Message... params) { int avatarPixel = activity.getResources().getDimensionPixelOffset(R.dimen.msg_avatar_size); return AvatarService.getInstance().get(params[0], avatarPixel, isCancelled()); } @Override protected void onPostExecute(Bitmap bitmap) { if (bitmap != null) { final ImageView imageView = imageViewReference.get(); if (imageView != null) { imageView.setImageBitmap(bitmap); imageView.setBackgroundColor(0x00000000); } } } } 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 bitmapWorkerTaskReference; public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) { super(res, bitmap); bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask); } public BitmapWorkerTask getBitmapWorkerTask() { return bitmapWorkerTaskReference.get(); } } }