diff --git a/app/src/main/java/org/linphone/chat/ChatMessageOldViewHolder.java b/app/src/main/java/org/linphone/chat/ChatMessageOldViewHolder.java new file mode 100644 index 000000000..af40cfc68 --- /dev/null +++ b/app/src/main/java/org/linphone/chat/ChatMessageOldViewHolder.java @@ -0,0 +1,116 @@ +/* +ChatMessageViewHolder.java +Copyright (C) 2017 Belledonne Communications, Grenoble, France + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +package org.linphone.chat; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import org.linphone.R; +import org.linphone.core.ChatMessage; + +public class ChatMessageOldViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + public String messageId; + public Context mContext; + public ChatMessage message; + public LinearLayout eventLayout; + public TextView eventMessage; + + public RelativeLayout bubbleLayout; + public LinearLayout separatorLayout; + public LinearLayout background; + public RelativeLayout avatarLayout; + public TextView contactName; + + public ImageView messageStatus; + public ProgressBar messageSendingInProgress; + public LinearLayout imdmLayout; + public ImageView imdmIcon; + public TextView imdmLabel; + + public TextView messageText; + public ImageView messageImage; + + public RelativeLayout fileTransferLayout; + public ProgressBar fileTransferProgressBar; + public Button fileTransferAction; + + public TextView fileName; + public Button openFileButton; + + public CheckBox delete; + private ClickListener mListener; + + public ChatMessageOldViewHolder(Context context, View view, ClickListener listener) { + this(view); + mContext = context; + mListener = listener; + view.setOnClickListener(this); + } + + public ChatMessageOldViewHolder(View view) { + super(view); + eventLayout = view.findViewById(R.id.event); + //eventTime = view.findViewById(R.id.event_date); + eventMessage = view.findViewById(R.id.event_text); + + bubbleLayout = view.findViewById(R.id.bubble); + background = view.findViewById(R.id.background); + avatarLayout = view.findViewById(R.id.avatar_layout); + contactName = view.findViewById(R.id.contact_header); + + messageStatus = view.findViewById(R.id.status); + messageSendingInProgress = view.findViewById(R.id.inprogress); + imdmLayout = view.findViewById(R.id.imdmLayout); + imdmIcon = view.findViewById(R.id.imdmIcon); + imdmLabel = view.findViewById(R.id.imdmText); + + messageText = view.findViewById(R.id.message); + messageImage = view.findViewById(R.id.image); + separatorLayout = view.findViewById(R.id.separator); + + fileTransferLayout = view.findViewById(R.id.file_transfer_layout); + fileTransferProgressBar = view.findViewById(R.id.progress_bar); + fileTransferAction = view.findViewById(R.id.file_transfer_action); + + fileName = view.findViewById(R.id.file_name); + openFileButton = view.findViewById(R.id.open_file); + + delete = view.findViewById(R.id.delete_message); + } + + @Override + public void onClick(View v) { + if (mListener != null) { + mListener.onItemClicked(getAdapterPosition()); + } + } + + public interface ClickListener { + void onItemClicked(int position); + } +} \ No newline at end of file diff --git a/app/src/main/java/org/linphone/chat/ChatMessagesOldAdapter.java b/app/src/main/java/org/linphone/chat/ChatMessagesOldAdapter.java new file mode 100644 index 000000000..592478008 --- /dev/null +++ b/app/src/main/java/org/linphone/chat/ChatMessagesOldAdapter.java @@ -0,0 +1,705 @@ +/* +ChatMessagesAdapter.java +Copyright (C) 2017 Belledonne Communications, Grenoble, France + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +package org.linphone.chat; + +import android.Manifest; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Matrix; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.media.ExifInterface; +import android.net.Uri; +import android.os.AsyncTask; +import android.provider.MediaStore; +import android.support.annotation.NonNull; +import android.support.v4.content.FileProvider; +import android.text.Spanned; +import android.text.method.LinkMovementMethod; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.MimeTypeMap; +import android.widget.ImageView; +import android.widget.RelativeLayout; + +import org.linphone.LinphoneActivity; +import org.linphone.LinphoneManager; +import org.linphone.R; +import org.linphone.compatibility.Compatibility; +import org.linphone.contacts.ContactsManager; +import org.linphone.contacts.LinphoneContact; +import org.linphone.core.Address; +import org.linphone.core.ChatMessage; +import org.linphone.core.ChatMessageListenerStub; +import org.linphone.core.Content; +import org.linphone.core.EventLog; +import org.linphone.core.LimeState; +import org.linphone.mediastream.Log; +import org.linphone.utils.FileUtils; +import org.linphone.utils.LinphoneUtils; +import org.linphone.utils.SelectableAdapter; +import org.linphone.utils.SelectableHelper; +import org.linphone.views.ContactAvatar; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION; + +public class ChatMessagesOldAdapter extends SelectableAdapter { + + private static int MARGIN_BETWEEN_MESSAGES = 10; + private static int SIDE_MARGIN = 100; + private Context mContext; + private List mHistory; + private List mParticipants; + private int mItemResource; + private Bitmap mDefaultBitmap; + private ChatMessagesFragment mFragment; + private ChatMessageListenerStub mListener; + + private ChatMessageViewHolder.ClickListener mClickListener; + + public ChatMessagesOldAdapter(ChatMessagesFragment fragment, SelectableHelper helper, int itemResource, EventLog[] history, ArrayList participants, ChatMessageViewHolder.ClickListener clickListener) { + super(helper); + mFragment = fragment; + mContext = mFragment.getActivity(); + mItemResource = itemResource; + mHistory = new ArrayList<>(Arrays.asList(history)); + Collections.reverse(mHistory); + mParticipants = participants; + mClickListener = clickListener; + mListener = new ChatMessageListenerStub() { + @Override + public void onFileTransferProgressIndication(ChatMessage message, Content content, int offset, int total) { + ChatMessageViewHolder holder = (ChatMessageViewHolder) message.getUserData(); + if (holder == null) return; + + if (offset == total) { + holder.fileTransferProgressBar.setVisibility(View.GONE); + holder.fileTransferAction.setVisibility(View.GONE); + holder.fileTransferLayout.setVisibility(View.GONE); + + displayAttachedFile(message, holder); + } else { + holder.fileTransferProgressBar.setVisibility(View.VISIBLE); + holder.fileTransferProgressBar.setProgress(offset * 100 / total); + } + } + + @Override + public void onMsgStateChanged(ChatMessage message, ChatMessage.State state) { + if (state == ChatMessage.State.FileTransferDone) { + if (!message.isOutgoing()) { + message.setAppdata(message.getFileTransferFilepath()); + } + message.setFileTransferFilepath(null); // Not needed anymore, will help differenciate between InProgress states for file transfer / message sending + } + for (int i = 0; i < mHistory.size(); i++) { + EventLog log = mHistory.get(i); + if (log.getType() == EventLog.Type.ConferenceChatMessage && log.getChatMessage() == message) { + notifyItemChanged(i); + break; + } + } + + } + }; + } + + @Override + public ChatMessageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View v = LayoutInflater.from(parent.getContext()) + .inflate(mItemResource, parent, false); + ChatMessageViewHolder VH = new ChatMessageViewHolder(mContext, v, mClickListener); + + //Allows onLongClick ContextMenu on bubbles + mFragment.registerForContextMenu(v); + v.setTag(VH); + return VH; + } + + @Override + public void onBindViewHolder(@NonNull final ChatMessageViewHolder holder, int position) { + final EventLog event = mHistory.get(position); + holder.eventLayout.setVisibility(View.GONE); + holder.bubbleLayout.setVisibility(View.GONE); + holder.delete.setVisibility(isEditionEnabled() ? View.VISIBLE : View.GONE); + holder.messageText.setVisibility(View.GONE); + holder.messageImage.setVisibility(View.GONE); + holder.fileTransferLayout.setVisibility(View.GONE); + holder.fileTransferProgressBar.setProgress(0); + holder.fileTransferAction.setEnabled(true); + holder.fileName.setVisibility(View.GONE); + holder.openFileButton.setVisibility(View.GONE); + holder.messageStatus.setVisibility(View.INVISIBLE); + holder.messageSendingInProgress.setVisibility(View.GONE); + holder.imdmLayout.setVisibility(View.INVISIBLE); + + if (isEditionEnabled()) { + holder.delete.setOnCheckedChangeListener(null); + holder.delete.setChecked(isSelected(position)); + holder.delete.setTag(position); + } + + if (event.getType() == EventLog.Type.ConferenceChatMessage) { + holder.bubbleLayout.setVisibility(View.VISIBLE); + final ChatMessage message = event.getChatMessage(); + + if (position > 0 && mContext.getResources().getBoolean(R.bool.lower_space_between_chat_bubbles_if_same_person)) { + EventLog previousEvent = (EventLog) getItem(position - 1); + if (previousEvent.getType() == EventLog.Type.ConferenceChatMessage) { + ChatMessage previousMessage = previousEvent.getChatMessage(); + if (previousMessage.getFromAddress().weakEqual(message.getFromAddress())) { + holder.separatorLayout.setVisibility(View.GONE); + } + } else { + // No separator if previous event is not a message + holder.separatorLayout.setVisibility(View.GONE); + } + } + + holder.messageId = message.getMessageId(); + message.setUserData(holder); + + RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT); + + ChatMessage.State status = message.getState(); + Address remoteSender = message.getFromAddress(); + String displayName; + + LinphoneContact contact = null; + if (message.isOutgoing()) { + message.setListener(mListener); + + if (status == ChatMessage.State.InProgress) { + holder.messageSendingInProgress.setVisibility(View.VISIBLE); + } + + if (!message.isSecured() && LinphoneManager.getLc().limeEnabled() == LimeState.Mandatory && status != ChatMessage.State.InProgress) { + holder.messageStatus.setVisibility(View.VISIBLE); + holder.messageStatus.setImageResource(R.drawable.chat_unsecure); + } + + if (status == ChatMessage.State.DeliveredToUser) { + holder.imdmLayout.setVisibility(View.VISIBLE); + holder.imdmIcon.setImageResource(R.drawable.imdn_received); + holder.imdmLabel.setText(R.string.delivered); + holder.imdmLabel.setTextColor(mContext.getResources().getColor(R.color.colorD)); + } else if (status == ChatMessage.State.Displayed) { + holder.imdmLayout.setVisibility(View.VISIBLE); + holder.imdmIcon.setImageResource(R.drawable.imdn_read); + holder.imdmLabel.setText(R.string.displayed); + holder.imdmLabel.setTextColor(mContext.getResources().getColor(R.color.colorK)); + } else if (status == ChatMessage.State.NotDelivered) { + holder.imdmLayout.setVisibility(View.VISIBLE); + holder.imdmIcon.setImageResource(R.drawable.imdn_error); + holder.imdmLabel.setText(R.string.error); + holder.imdmLabel.setTextColor(mContext.getResources().getColor(R.color.colorI)); + } else if (status == ChatMessage.State.FileTransferError) { + holder.imdmLayout.setVisibility(View.VISIBLE); + holder.imdmIcon.setImageResource(R.drawable.imdn_error); + holder.imdmLabel.setText(R.string.file_transfer_error); + holder.imdmLabel.setTextColor(mContext.getResources().getColor(R.color.colorI)); + } + + //layoutParams allow bubbles alignment during selection mode + if (isEditionEnabled()) { + layoutParams.addRule(RelativeLayout.LEFT_OF, holder.delete.getId()); + layoutParams.setMargins(SIDE_MARGIN, MARGIN_BETWEEN_MESSAGES / 2, 0, MARGIN_BETWEEN_MESSAGES / 2); + } else { + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); + layoutParams.setMargins(SIDE_MARGIN, MARGIN_BETWEEN_MESSAGES / 2, 0, MARGIN_BETWEEN_MESSAGES / 2); + } + + holder.background.setBackgroundResource(R.drawable.resizable_chat_bubble_outgoing); + Compatibility.setTextAppearance(holder.contactName, mContext, R.style.font3); + Compatibility.setTextAppearance(holder.fileTransferAction, mContext, R.style.font15); + holder.fileTransferAction.setBackgroundResource(R.drawable.resizable_confirm_delete_button); + ContactAvatar.setAvatarMask(holder.avatarLayout, R.drawable.avatar_chat_mask_outgoing); + } else { + for (LinphoneContact c : mParticipants) { + if (c != null && c.hasAddress(remoteSender.asStringUriOnly())) { + contact = c; + break; + } + } + + if (isEditionEnabled()) { + layoutParams.addRule(RelativeLayout.LEFT_OF, holder.delete.getId()); + layoutParams.setMargins(SIDE_MARGIN, MARGIN_BETWEEN_MESSAGES / 2, 0, MARGIN_BETWEEN_MESSAGES / 2); + } else { + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT); + layoutParams.setMargins(0, MARGIN_BETWEEN_MESSAGES / 2, SIDE_MARGIN, MARGIN_BETWEEN_MESSAGES / 2); + } + + holder.background.setBackgroundResource(R.drawable.resizable_chat_bubble_incoming); + Compatibility.setTextAppearance(holder.contactName, mContext, R.style.font9); + Compatibility.setTextAppearance(holder.fileTransferAction, mContext, R.style.font8); + holder.fileTransferAction.setBackgroundResource(R.drawable.resizable_assistant_button); + ContactAvatar.setAvatarMask(holder.avatarLayout, R.drawable.avatar_chat_mask); + } + + if (contact == null) { + contact = ContactsManager.getInstance().findContactFromAddress(remoteSender); + } + if (contact != null) { + if (contact.getFullName() != null) { + displayName = contact.getFullName(); + } else { + displayName = LinphoneUtils.getAddressDisplayName(remoteSender); + } + ContactAvatar.displayAvatar(contact, holder.avatarLayout); + } else { + displayName = LinphoneUtils.getAddressDisplayName(remoteSender); + ContactAvatar.displayAvatar(displayName, holder.avatarLayout); + } + holder.contactName.setText(LinphoneUtils.timestampToHumanDate(mContext, message.getTime(), R.string.messages_date_format) + " - " + displayName); + + if (message.hasTextContent()) { + String msg = message.getTextContent(); + Spanned text = LinphoneUtils.getTextWithHttpLinks(msg); + holder.messageText.setText(text); + holder.messageText.setMovementMethod(LinkMovementMethod.getInstance()); + holder.messageText.setVisibility(View.VISIBLE); + } + + String externalBodyUrl = message.getExternalBodyUrl(); + Content fileTransferContent = message.getFileTransferInformation(); + + boolean hasFile = message.getAppdata() != null; + boolean hasFileTransfer = externalBodyUrl != null; + for (Content c : message.getContents()) { + if (c.isFile()) { + hasFile = true; + } else if (c.isFileTransfer()) { + hasFileTransfer = true; + } + } + if (hasFile) { // Something to display + displayAttachedFile(message, holder); + } + + if (hasFileTransfer) { // Incoming file transfer not yet downloaded + holder.fileName.setVisibility(View.VISIBLE); + holder.fileName.setText(fileTransferContent.getName()); + + holder.fileTransferLayout.setVisibility(View.VISIBLE); + holder.fileTransferProgressBar.setVisibility(View.GONE); + if (message.isFileTransferInProgress()) { // Incoming file transfer in progress + holder.fileTransferAction.setVisibility(View.GONE); + } else { + holder.fileTransferAction.setText(mContext.getString(R.string.accept)); + holder.fileTransferAction.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mContext.getPackageManager().checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, mContext.getPackageName()) == PackageManager.PERMISSION_GRANTED) { + v.setEnabled(false); + String filename = message.getFileTransferInformation().getName(); + File file = new File(FileUtils.getStorageDirectory(mContext), filename); + int prefix = 1; + while (file.exists()) { + file = new File(FileUtils.getStorageDirectory(mContext), prefix + "_" + filename); + Log.w("File with that name already exists, renamed to " + prefix + "_" + filename); + prefix += 1; + } + message.setListener(mListener); + message.setFileTransferFilepath(file.getPath()); + message.downloadFile(); + + } else { + Log.w("WRITE_EXTERNAL_STORAGE permission not granted, won't be able to store the downloaded file"); + LinphoneActivity.instance().checkAndRequestExternalStoragePermission(); + } + } + }); + } + } else if (message.isFileTransferInProgress()) { // Outgoing file transfer in progress + message.setListener(mListener); // add the listener for file upload progress display + holder.messageSendingInProgress.setVisibility(View.GONE); + holder.fileTransferLayout.setVisibility(View.VISIBLE); + holder.fileTransferAction.setText(mContext.getString(R.string.cancel)); + holder.fileTransferAction.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + message.cancelFileTransfer(); + notifyItemChanged(holder.getAdapterPosition()); + } + }); + } + + holder.bubbleLayout.setLayoutParams(layoutParams); + } else { // Event is not chat message + holder.eventLayout.setVisibility(View.VISIBLE); + holder.eventMessage.setTextColor(mContext.getResources().getColor(R.color.colorE)); + holder.eventLayout.setBackgroundResource(R.drawable.event_decoration_gray); + holder.eventLayout.setBackgroundResource(R.drawable.event_decoration_gray); + + Address address = event.getParticipantAddress(); + if (address == null && event.getType() == EventLog.Type.ConferenceSecurityEvent) { + address = event.getSecurityEventFaultyDeviceAddress(); + } + String displayName = ""; + if (address != null) { + LinphoneContact contact = ContactsManager.getInstance().findContactFromAddress(address); + if (contact != null) { + displayName = contact.getFullName(); + } else { + displayName = LinphoneUtils.getAddressDisplayName(address); + } + } + + switch (event.getType()) { + case ConferenceCreated: + holder.eventMessage.setText(mContext.getString(R.string.conference_created)); + break; + case ConferenceTerminated: + holder.eventMessage.setText(mContext.getString(R.string.conference_destroyed)); + break; + case ConferenceParticipantAdded: + holder.eventMessage.setText(mContext.getString(R.string.participant_added).replace("%s", displayName)); + break; + case ConferenceParticipantRemoved: + holder.eventMessage.setText(mContext.getString(R.string.participant_removed).replace("%s", displayName)); + break; + case ConferenceSubjectChanged: + holder.eventMessage.setText(mContext.getString(R.string.subject_changed).replace("%s", event.getSubject())); + break; + case ConferenceParticipantSetAdmin: + holder.eventMessage.setText(mContext.getString(R.string.admin_set).replace("%s", displayName)); + break; + case ConferenceParticipantUnsetAdmin: + holder.eventMessage.setText(mContext.getString(R.string.admin_unset).replace("%s", displayName)); + break; + case ConferenceParticipantDeviceAdded: + holder.eventMessage.setText(mContext.getString(R.string.device_added).replace("%s", displayName)); + break; + case ConferenceParticipantDeviceRemoved: + holder.eventMessage.setText(mContext.getString(R.string.device_removed).replace("%s", displayName)); + break; + case ConferenceSecurityEvent: + holder.eventMessage.setTextColor(mContext.getResources().getColor(R.color.colorI)); + holder.eventLayout.setBackgroundResource(R.drawable.event_decoration_red); + holder.eventLayout.setBackgroundResource(R.drawable.event_decoration_red); + + switch (event.getSecurityEventType()) { + case EncryptionIdentityKeyChanged: + holder.eventMessage.setText(mContext.getString(R.string.lime_identity_key_changed).replace("%s", displayName)); + break; + case ManInTheMiddleDetected: + holder.eventMessage.setText(mContext.getString(R.string.man_in_the_middle_detected).replace("%s", displayName)); + break; + case SecurityLevelDowngraded: + holder.eventMessage.setText(mContext.getString(R.string.security_level_downgraded).replace("%s", displayName)); + break; + case ParticipantMaxDeviceCountExceeded: + holder.eventMessage.setText(mContext.getString(R.string.participant_max_count_exceeded).replace("%s", displayName)); + break; + case None: + default: + break; + } + break; + case None: + default: + holder.eventMessage.setText(mContext.getString(R.string.unexpected_event).replace("%s", displayName).replace("%i", String.valueOf(event.getType().toInt()))); + break; + } + } + } + + @Override + public int getItemCount() { + return mHistory.size(); + } + + public void addToHistory(EventLog log) { + mHistory.add(0, log); + notifyItemInserted(0); + } + + public void addAllToHistory(ArrayList logs) { + int currentSize = mHistory.size() - 1; + Collections.reverse(logs); + mHistory.addAll(logs); + notifyItemRangeInserted(currentSize + 1, logs.size()); + } + + public void setContacts(ArrayList participants) { + mParticipants = participants; + } + + public void refresh(EventLog[] history) { + mHistory = new ArrayList<>(Arrays.asList(history)); + Collections.reverse(mHistory); + notifyDataSetChanged(); + } + + public void clear() { + for (EventLog event : mHistory) { + if (event.getType() == EventLog.Type.ConferenceChatMessage) { + ChatMessage message = event.getChatMessage(); + message.setListener(null); + } + } + mHistory.clear(); + } + + public int getCount() { + return mHistory.size(); + } + + public Object getItem(int i) { + return mHistory.get(i); + } + + public void removeItem(int i) { + mHistory.remove(i); + notifyItemRemoved(i); + } + + private void loadBitmap(String path, ImageView imageView) { + if (cancelPotentialWork(path, imageView)) { + mDefaultBitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.chat_file); + BitmapWorkerTask task = new BitmapWorkerTask(imageView); + final AsyncBitmap asyncBitmap = new AsyncBitmap(mContext.getResources(), mDefaultBitmap, task); + imageView.setImageDrawable(asyncBitmap); + task.execute(path); + } + } + + private void openFile(String path) { + Intent intent = new Intent(Intent.ACTION_VIEW); + File file; + Uri contentUri; + if (path.startsWith("file://")) { + path = path.substring("file://".length()); + file = new File(path); + contentUri = FileProvider.getUriForFile(mContext, mContext.getResources().getString(R.string.file_provider), file); + } else if (path.startsWith("content://")) { + contentUri = Uri.parse(path); + } else { + file = new File(path); + try { + contentUri = FileProvider.getUriForFile(mContext, mContext.getResources().getString(R.string.file_provider), file); + } catch (Exception e) { + contentUri = Uri.parse(path); + } + } + String type = null; + String extension = MimeTypeMap.getFileExtensionFromUrl(contentUri.toString()); + if (extension != null) { + type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); + } + if (type != null) { + intent.setDataAndType(contentUri, type); + } else { + intent.setDataAndType(contentUri, "*/*"); + } + intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION); + mContext.startActivity(intent); + } + + private void displayAttachedFile(ChatMessage message, ChatMessageViewHolder holder) { + holder.fileName.setVisibility(View.VISIBLE); + + String appData = message.getAppdata(); + if (appData == null) { + for (Content c : message.getContents()) { + if (c.isFile()) { + appData = c.getFilePath(); + } + } + } + + if (appData != null) { + FileUtils.scanFile(message); + holder.fileName.setText(FileUtils.getNameFromFilePath(appData)); + if (FileUtils.isExtensionImage(appData)) { + holder.messageImage.setVisibility(View.VISIBLE); + loadBitmap(appData, holder.messageImage); + holder.messageImage.setTag(appData); + } else { + holder.openFileButton.setVisibility(View.VISIBLE); + holder.openFileButton.setTag(appData); + holder.openFileButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + openFile((String) v.getTag()); + } + }); + } + } + } + + /* + * Bitmap related classes and methods + */ + + private class BitmapWorkerTask extends AsyncTask { + private final WeakReference imageViewReference; + public String path; + + public BitmapWorkerTask(ImageView imageView) { + path = null; + // Use a WeakReference to ensure the ImageView can be garbage collected + imageViewReference = new WeakReference<>(imageView); + } + + private Bitmap scaleToFitHeight(Bitmap b, int height) { + float factor = height / (float) b.getHeight(); + return Bitmap.createScaledBitmap(b, (int) (b.getWidth() * factor), height, true); + } + + // Decode image in background. + @Override + protected Bitmap doInBackground(String... params) { + path = params[0]; + Bitmap bm = null; + Bitmap thumbnail = null; + if (FileUtils.isExtensionImage(path)) { + if (path.startsWith("content")) { + try { + bm = MediaStore.Images.Media.getBitmap(mContext.getContentResolver(), Uri.parse(path)); + } catch (FileNotFoundException e) { + Log.e(e); + } catch (IOException e) { + Log.e(e); + } + } else { + bm = BitmapFactory.decodeFile(path); + } + + ImageView imageView = imageViewReference.get(); + try { + // Rotate the bitmap if possible/needed, using EXIF data + Matrix matrix = new Matrix(); + ExifInterface exif = new ExifInterface(path); + int pictureOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0); + if (pictureOrientation == 6 || pictureOrientation == 3 || pictureOrientation == 8) { + if (pictureOrientation == 6) { + matrix.postRotate(90); + } else if (pictureOrientation == 3) { + matrix.postRotate(180); + } else { + matrix.postRotate(270); + } + if (pictureOrientation == 6 || pictureOrientation == 8) { + matrix.postScale(1, imageView.getMeasuredHeight() / (float) bm.getHeight()); + } else { + matrix.postScale(imageView.getMeasuredHeight() / (float) bm.getHeight(), 1); + } + thumbnail = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true); + if (thumbnail != bm) { + bm.recycle(); + bm = null; + } + } + } catch (Exception e) { + Log.e(e); + } + + if (thumbnail == null && bm != null) { + thumbnail = scaleToFitHeight(bm, imageView.getMeasuredHeight()); + bm.recycle(); + } + return thumbnail; + } else { + return mDefaultBitmap; + } + } + + // Once complete, see if ImageView is still around and set bitmap. + @Override + protected void onPostExecute(Bitmap bitmap) { + if (isCancelled()) { + bitmap.recycle(); + bitmap = null; + } + if (imageViewReference != null && bitmap != null) { + final ImageView imageView = imageViewReference.get(); + final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); + if (this == bitmapWorkerTask && imageView != null) { + imageView.setImageBitmap(bitmap); + imageView.setTag(path); + imageView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + openFile((String) v.getTag()); + } + }); + } + } + } + } + + class AsyncBitmap extends BitmapDrawable { + private final WeakReference bitmapWorkerTaskReference; + + public AsyncBitmap(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) { + super(res, bitmap); + bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask); + } + + public BitmapWorkerTask getBitmapWorkerTask() { + return bitmapWorkerTaskReference.get(); + } + } + + private boolean cancelPotentialWork(String path, ImageView imageView) { + final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); + + if (bitmapWorkerTask != null) { + final String bitmapData = bitmapWorkerTask.path; + // If bitmapData is not yet set or it differs from the new data + if (bitmapData == null || bitmapData != path) { + // Cancel previous task + bitmapWorkerTask.cancel(true); + } else { + // The same work is already in progress + return false; + } + } + // No task associated with the ImageView, or an existing task was cancelled + return true; + } + + private BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { + if (imageView != null) { + final Drawable drawable = imageView.getDrawable(); + if (drawable instanceof AsyncBitmap) { + final AsyncBitmap asyncDrawable = (AsyncBitmap) drawable; + return asyncDrawable.getBitmapWorkerTask(); + } + } + return null; + } +}