diff --git a/res/layout/chat_bubble.xml b/res/layout/chat_bubble.xml
new file mode 100644
index 000000000..1b0bc6601
--- /dev/null
+++ b/res/layout/chat_bubble.xml
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/chat_bubble_incoming.xml b/res/layout/chat_bubble_incoming.xml
deleted file mode 100644
index d3912a238..000000000
--- a/res/layout/chat_bubble_incoming.xml
+++ /dev/null
@@ -1,114 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/res/layout/chat_bubble_outgoing.xml b/res/layout/chat_bubble_outgoing.xml
deleted file mode 100644
index a96d18751..000000000
--- a/res/layout/chat_bubble_outgoing.xml
+++ /dev/null
@@ -1,112 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/org/linphone/ChatFragment.java b/src/org/linphone/ChatFragment.java
index 2f6ccb60e..0fe954352 100644
--- a/src/org/linphone/ChatFragment.java
+++ b/src/org/linphone/ChatFragment.java
@@ -21,8 +21,13 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Calendar;
import java.util.List;
import java.util.Locale;
@@ -37,8 +42,8 @@ import org.linphone.core.LinphoneCore;
import org.linphone.core.LinphoneCoreFactory;
import org.linphone.core.LinphoneCoreListenerBase;
import org.linphone.mediastream.Log;
-import org.linphone.ui.BubbleChat;
+import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
@@ -48,13 +53,18 @@ import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
import android.media.ExifInterface;
+import android.media.ThumbnailUtils;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -63,7 +73,9 @@ import android.os.Parcelable;
import android.provider.MediaStore;
import android.support.v4.content.CursorLoader;
import android.text.Editable;
+import android.text.Spanned;
import android.text.TextWatcher;
+import android.text.method.LinkMovementMethod;
import android.view.ContextMenu;
import android.view.Gravity;
import android.view.LayoutInflater;
@@ -83,6 +95,7 @@ import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
+import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
@@ -113,6 +126,7 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC
private SearchContactsListAdapter searchAdapter;
private ListView messagesList, resultContactsSearch;
private LayoutInflater inflater;
+ private Bitmap defaultBitmap;
private boolean isEditMode = false;
private LinphoneContact contact;
@@ -145,6 +159,8 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC
}
//Initialize UI
+ defaultBitmap = BitmapFactory.decodeResource(getActivity().getResources(), R.drawable.chat_picture_over);
+
contactName = (TextView) view.findViewById(R.id.contact_name);
messagesList = (ListView) view.findViewById(R.id.chat_message_list);
messagesList.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE);
@@ -322,130 +338,6 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC
}
}
- class ChatMessageAdapter extends BaseAdapter {
- ArrayList history;
- Context context;
-
- public ChatMessageAdapter(Context c) {
- context = c;
- history = new ArrayList();
- refreshHistory();
- }
-
- public void destroy() {
- if (history != null) {
- history.clear();
- }
- }
-
- public void refreshHistory() {
- history.clear();
- LinphoneChatMessage[] messages = chatRoom.getHistory();
- history.addAll(Arrays.asList(messages));
- notifyDataSetChanged();
- }
-
- public void addMessage(LinphoneChatMessage message) {
- history.add(message);
- notifyDataSetChanged();
- }
-
- @Override
- public int getCount() {
- return history.size();
- }
-
- @Override
- public LinphoneChatMessage getItem(int position) {
- return history.get(position);
- }
-
- @Override
- public long getItemId(int position) {
- return history.get(position).getStorageId();
- }
-
- @Override
- public View getView(final int position, View convertView, ViewGroup parent) {
- LinphoneChatMessage message = history.get(position);
- RelativeLayout rlayout;
-
- if (convertView != null) {
- rlayout = (RelativeLayout) convertView;
- View bbv = rlayout.getChildAt(0);
- rlayout.removeAllViews();
- BubbleChat bbc = (BubbleChat) bbv.getTag();
- bbc.destroy();
- } else {
- rlayout = new RelativeLayout(context);
- }
- BubbleChat bubble = new BubbleChat(context, message, contact);
- View v = bubble.getView();
- v.setTag(bubble);
-
- registerForContextMenu(v);
-
- CheckBox deleteChatBubble = (CheckBox) v.findViewById(R.id.delete_message);
-
- if (isEditMode) {
- deleteChatBubble.setVisibility(View.VISIBLE);
- if (message.isOutgoing()) {
- RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
- layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
- layoutParams.setMargins(100, 10, 10, 10);
- v.setLayoutParams(layoutParams);
- } else {
- LinearLayout message_layout = (LinearLayout) v.findViewById(R.id.message_content);
- message_layout.setGravity(Gravity.RIGHT);
- }
-
- deleteChatBubble.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
- messagesList.setItemChecked(position, b);
- if (getNbItemsChecked() == getCount()) {
- deselectAll.setVisibility(View.VISIBLE);
- selectAll.setVisibility(View.GONE);
- enabledDeleteButton(true);
- } else {
- if (getNbItemsChecked() == 0) {
- deselectAll.setVisibility(View.GONE);
- selectAll.setVisibility(View.VISIBLE);
- enabledDeleteButton(false);
- } else {
- deselectAll.setVisibility(View.GONE);
- selectAll.setVisibility(View.VISIBLE);
- enabledDeleteButton(true);
- }
- }
- }
- });
-
- if (messagesList.isItemChecked(position)) {
- deleteChatBubble.setChecked(true);
- } else {
- deleteChatBubble.setChecked(false);
- }
-
- rlayout.addView(v);
- } else {
- if (message.isOutgoing()) {
- RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
- layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
- layoutParams.setMargins(100, 10, 10, 10);
- v.setLayoutParams(layoutParams);
- } else {
- RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
- layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
- layoutParams.setMargins(10, 10, 100, 10);
- v.setLayoutParams(layoutParams);
- }
- rlayout.addView(v);
- }
- return rlayout;
- }
- }
-
public void initChatRoom(String sipUri) {
LinphoneCore lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
@@ -582,6 +474,10 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC
if (adapter != null) {
adapter.destroy();
}
+ if (defaultBitmap != null) {
+ defaultBitmap.recycle();
+ defaultBitmap = null;
+ }
super.onDestroy();
}
@@ -1042,6 +938,472 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC
searchAdapter.notifyDataSetChanged();
}
+ class ChatMessageAdapter extends BaseAdapter {
+ private class ViewHolder implements LinphoneChatMessage.LinphoneChatMessageListener {
+ public int id;
+ public RelativeLayout bubbleLayout;
+ public CheckBox delete;
+ public LinearLayout background;
+ public ImageView contactPicture;
+ public TextView contactName;
+ public TextView messageText;
+ public ImageView messageImage;
+ public RelativeLayout fileTransferLayout;
+ public ProgressBar fileTransferProgressBar;
+ public Button fileTransferAction;
+ public ImageView messageStatus;
+ public ProgressBar messageSendingInProgress;
+
+ public ViewHolder(View view) {
+ id = view.getId();
+ bubbleLayout = (RelativeLayout) view.findViewById(R.id.bubble);
+ delete = (CheckBox) view.findViewById(R.id.delete_message);
+ background = (LinearLayout) view.findViewById(R.id.background);
+ contactPicture = (ImageView) view.findViewById(R.id.contact_picture);
+ contactName = (TextView) view.findViewById(R.id.contact_header);
+ messageText = (TextView) view.findViewById(R.id.message);
+ messageImage = (ImageView) view.findViewById(R.id.image);
+ fileTransferLayout = (RelativeLayout) view.findViewById(R.id.file_transfer_layout);
+ fileTransferProgressBar = (ProgressBar) view.findViewById(R.id.progress_bar);
+ fileTransferAction = (Button) view.findViewById(R.id.file_transfer_action);
+ messageStatus = (ImageView) view.findViewById(R.id.status);
+ messageSendingInProgress = (ProgressBar) view.findViewById(R.id.inprogress);
+ }
+
+ @Override
+ public void onLinphoneChatMessageStateChanged(LinphoneChatMessage msg, State state) {
+
+ }
+
+ @Override
+ public void onLinphoneChatMessageFileTransferReceived(LinphoneChatMessage msg, LinphoneContent content, LinphoneBuffer buffer) {
+
+ }
+
+ @Override
+ public void onLinphoneChatMessageFileTransferSent(LinphoneChatMessage msg, LinphoneContent content, int offset, int size, LinphoneBuffer bufferToFill) {
+
+ }
+
+ @Override
+ public void onLinphoneChatMessageFileTransferProgressChanged(LinphoneChatMessage msg, LinphoneContent content, int offset, int total) {
+ if (msg.getStorageId() == id) fileTransferProgressBar.setProgress(offset * 100 / total);
+ }
+ }
+
+ ArrayList history;
+ Context context;
+
+ public ChatMessageAdapter(Context c) {
+ context = c;
+ history = new ArrayList();
+ refreshHistory();
+ }
+
+ public void destroy() {
+ if (history != null) {
+ history.clear();
+ }
+ }
+
+ public void refreshHistory() {
+ history.clear();
+ LinphoneChatMessage[] messages = chatRoom.getHistory();
+ history.addAll(Arrays.asList(messages));
+ notifyDataSetChanged();
+ }
+
+ public void addMessage(LinphoneChatMessage message) {
+ history.add(message);
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getCount() {
+ return history.size();
+ }
+
+ @Override
+ public LinphoneChatMessage getItem(int position) {
+ return history.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return history.get(position).getStorageId();
+ }
+
+ @Override
+ public View getView(final int position, View convertView, ViewGroup parent) {
+ final LinphoneChatMessage message = history.get(position);
+ View view = null;
+ final ViewHolder holder;
+
+ if (convertView != null) {
+ view = convertView;
+ holder = (ViewHolder) view.getTag();
+ LinphoneManager.removeListener(holder);
+ } else {
+ view = LayoutInflater.from(context).inflate(R.layout.chat_bubble, null);
+ holder = new ViewHolder(view);
+ view.setTag(holder);
+ }
+ view.setId(message.getStorageId());
+ holder.id = message.getStorageId();
+ registerForContextMenu(view);
+
+ LinphoneChatMessage.State status = message.getStatus();
+ String externalBodyUrl = message.getExternalBodyUrl();
+ LinphoneContent fileTransferContent = message.getFileTransferInformation();
+
+ holder.delete.setVisibility(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.messageStatus.setVisibility(View.INVISIBLE);
+ holder.messageSendingInProgress.setVisibility(View.GONE);
+
+ String displayName = message.getFrom().getUserName();
+ if (!message.isOutgoing()) {
+ if (contact != null) {
+ displayName = contact.getFullName();
+ if (contact.hasPhoto()) {
+ Bitmap photo = contact.getPhoto();
+ if (photo != null) {
+ holder.contactPicture.setImageBitmap(photo);
+ } else {
+ LinphoneUtils.setImagePictureFromUri(getActivity(), holder.contactPicture, contact.getPhotoUri(), contact.getThumbnailUri());
+ }
+ } else {
+ holder.contactPicture.setImageResource(R.drawable.avatar);
+ }
+ } else {
+ holder.contactPicture.setImageResource(R.drawable.avatar);
+ }
+ } else {
+ holder.contactPicture.setImageResource(R.drawable.avatar);
+ }
+
+ holder.contactName.setText(timestampToHumanDate(context, message.getTime()) + " - " + displayName);
+
+ if (status == LinphoneChatMessage.State.NotDelivered) {
+ holder.messageStatus.setVisibility(View.VISIBLE);
+ holder.messageStatus.setImageResource(R.drawable.chat_message_not_delivered);
+ } else if (status == LinphoneChatMessage.State.InProgress) {
+ holder.messageSendingInProgress.setVisibility(View.VISIBLE);
+ }
+
+ if (externalBodyUrl != null || fileTransferContent != null) {
+ holder.messageText.setVisibility(View.GONE);
+ holder.fileTransferProgressBar.setProgress(0);
+ String appData = message.getAppData();
+
+ if (message.isOutgoing() && appData != null){
+ holder.messageImage.setVisibility(View.VISIBLE);
+ loadBitmap(appData, holder.messageImage);
+
+ if (LinphoneManager.getInstance().getMessageUploadPending() != null && LinphoneManager.getInstance().getMessageUploadPending().getStorageId() == message.getStorageId()) {
+ holder.messageSendingInProgress.setVisibility(View.GONE);
+ holder.fileTransferLayout.setVisibility(View.VISIBLE);
+ LinphoneManager.addListener(holder);
+ }
+ } else {
+ if (appData != null && !LinphoneManager.getInstance().isMessagePending(message) && appData.contains(context.getString(R.string.temp_photo_name_with_date).split("%s")[0])) {
+ appData = null;
+ }
+
+ if (appData == null) {
+ LinphoneManager.addListener(holder);
+ holder.fileTransferLayout.setVisibility(View.VISIBLE);
+ } else {
+ if (LinphoneManager.getInstance().isMessagePending(message)) {
+ LinphoneManager.addListener(holder);
+ holder.fileTransferAction.setEnabled(false);
+ holder.fileTransferLayout.setVisibility(View.VISIBLE);
+ } else {
+ LinphoneManager.removeListener(holder);
+ holder.fileTransferLayout.setVisibility(View.GONE);
+ holder.messageImage.setVisibility(View.VISIBLE);
+ loadBitmap(appData, holder.messageImage);
+ }
+ }
+ }
+ } else {
+ Spanned text = null;
+ String msg = message.getText();
+ if (msg != null) {
+ text = getTextWithHttpLinks(msg);
+ holder.messageText.setText(text);
+ holder.messageText.setMovementMethod(LinkMovementMethod.getInstance());
+ holder.messageText.setVisibility(View.VISIBLE);
+ }
+ }
+
+ if (message.isOutgoing()) {
+ holder.fileTransferAction.setText(getString(R.string.cancel));
+ holder.fileTransferAction.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (LinphoneManager.getInstance().getMessageUploadPending() != null) {
+ holder.fileTransferProgressBar.setVisibility(View.GONE);
+ holder.fileTransferProgressBar.setProgress(0);
+ message.cancelFileTransfer();
+ LinphoneManager.getInstance().setUploadPendingFileMessage(null);
+ }
+ }
+ });
+ } else {
+ holder.fileTransferAction.setText(getString(R.string.accept));
+ holder.fileTransferAction.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (context.getPackageManager().checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, context.getPackageName()) == PackageManager.PERMISSION_GRANTED) {
+ v.setEnabled(false);
+ String extension = message.getFileTransferInformation().getSubtype();
+ String filename = context.getString(R.string.temp_photo_name_with_date).replace("%s", String.valueOf(System.currentTimeMillis())) + "." + extension;
+ File file = new File(Environment.getExternalStorageDirectory(), filename);
+ message.setAppData(filename);
+ LinphoneManager.getInstance().addDownloadMessagePending(message);
+ message.setListener(LinphoneManager.getInstance());
+ 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();
+ }
+ }
+ });
+ }
+
+ RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
+ if (message.isOutgoing()) {
+ layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
+ layoutParams.setMargins(100, 10, 10, 10);
+ holder.background.setBackgroundResource(R.drawable.resizable_chat_bubble_outgoing);
+ holder.contactName.setTextAppearance(R.style.font3);
+ holder.fileTransferAction.setTextAppearance(R.style.font15);
+ holder.fileTransferAction.setBackgroundResource(R.drawable.resizable_confirm_delete_button);
+ } else {
+ if (isEditMode) {
+ layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
+ layoutParams.setMargins(100, 10, 10, 10);
+ } else {
+ layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
+ layoutParams.setMargins(10, 10, 100, 10);
+ }
+ holder.background.setBackgroundResource(R.drawable.resizable_chat_bubble_incoming);
+ holder.contactName.setTextAppearance(R.style.font9);
+ holder.fileTransferAction.setTextAppearance(R.style.font8);
+ holder.fileTransferAction.setBackgroundResource(R.drawable.resizable_assistant_button);
+ }
+ holder.bubbleLayout.setLayoutParams(layoutParams);
+
+ if (isEditMode) {
+ holder.delete.setVisibility(View.VISIBLE);
+ holder.delete.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
+ messagesList.setItemChecked(position, b);
+ if (getNbItemsChecked() == getCount()) {
+ deselectAll.setVisibility(View.VISIBLE);
+ selectAll.setVisibility(View.GONE);
+ enabledDeleteButton(true);
+ } else {
+ if (getNbItemsChecked() == 0) {
+ deselectAll.setVisibility(View.GONE);
+ selectAll.setVisibility(View.VISIBLE);
+ enabledDeleteButton(false);
+ } else {
+ deselectAll.setVisibility(View.GONE);
+ selectAll.setVisibility(View.VISIBLE);
+ enabledDeleteButton(true);
+ }
+ }
+ }
+ });
+
+ if (messagesList.isItemChecked(position)) {
+ holder.delete.setChecked(true);
+ } else {
+ holder.delete.setChecked(false);
+ }
+ }
+
+ return view;
+ }
+
+ private String timestampToHumanDate(Context context, long timestamp) {
+ try {
+ Calendar cal = Calendar.getInstance();
+ cal.setTimeInMillis(timestamp);
+
+ SimpleDateFormat dateFormat;
+ if (isToday(cal)) {
+ dateFormat = new SimpleDateFormat(context.getResources().getString(R.string.today_date_format));
+ } else {
+ dateFormat = new SimpleDateFormat(context.getResources().getString(R.string.messages_date_format));
+ }
+
+ return dateFormat.format(cal.getTime());
+ } catch (NumberFormatException nfe) {
+ return String.valueOf(timestamp);
+ }
+ }
+
+ private boolean isToday(Calendar cal) {
+ return isSameDay(cal, Calendar.getInstance());
+ }
+
+ private boolean isSameDay(Calendar cal1, Calendar cal2) {
+ if (cal1 == null || cal2 == null) {
+ return false;
+ }
+
+ return (cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA) &&
+ cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
+ cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR));
+ }
+
+ private Spanned getTextWithHttpLinks(String text) {
+ if (text.contains("<")) {
+ text = text.replace("<", "<");
+ }
+ if (text.contains(">")) {
+ text = text.replace(">", ">");
+ }
+ if (text.contains("http://")) {
+ int indexHttp = text.indexOf("http://");
+ int indexFinHttp = text.indexOf(" ", indexHttp) == -1 ? text.length() : text.indexOf(" ", indexHttp);
+ String link = text.substring(indexHttp, indexFinHttp);
+ String linkWithoutScheme = link.replace("http://", "");
+ text = text.replaceFirst(link, "" + linkWithoutScheme + "");
+ }
+ if (text.contains("https://")) {
+ int indexHttp = text.indexOf("https://");
+ int indexFinHttp = text.indexOf(" ", indexHttp) == -1 ? text.length() : text.indexOf(" ", indexHttp);
+ String link = text.substring(indexHttp, indexFinHttp);
+ String linkWithoutScheme = link.replace("https://", "");
+ text = text.replaceFirst(link, "" + linkWithoutScheme + "");
+ }
+
+ return Compatibility.fromHtml(text);
+ }
+
+ public void loadBitmap(String path, ImageView imageView) {
+ if (cancelPotentialWork(path, imageView)) {
+ BitmapWorkerTask task = new BitmapWorkerTask(imageView);
+ final AsyncBitmap asyncBitmap = new AsyncBitmap(context.getResources(), defaultBitmap, task);
+ imageView.setImageDrawable(asyncBitmap);
+ task.execute(path);
+ }
+ }
+
+ 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);
+ }
+
+ // Decode image in background.
+ @Override
+ protected Bitmap doInBackground(String... params) {
+ path = params[0];
+ Bitmap bm = null;
+
+ if (path.startsWith("content")) {
+ try {
+ bm = MediaStore.Images.Media.getBitmap(context.getContentResolver(), Uri.parse(path));
+ } catch (FileNotFoundException e) {
+ Log.e(e);
+ } catch (IOException e) {
+ Log.e(e);
+ }
+ } else {
+ bm = BitmapFactory.decodeFile(path);
+ path = "file://" + path;
+ }
+
+ if (bm != null) {
+ bm = ThumbnailUtils.extractThumbnail(bm, SIZE_MAX, SIZE_MAX);
+ }
+ return bm;
+ }
+
+ // Once complete, see if ImageView is still around and set bitmap.
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ if (isCancelled()) {
+ 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 OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setDataAndType(Uri.parse((String)v.getTag()), "image/*");
+ context.startActivity(intent);
+ }
+ });
+ }
+ }
+ }
+ }
+
+ 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;
+ }
+ }
+
class SearchContactsListAdapter extends BaseAdapter {
private class ViewHolder {
public TextView name;
diff --git a/src/org/linphone/SettingsFragment.java b/src/org/linphone/SettingsFragment.java
index 0e3844689..3b5d90324 100644
--- a/src/org/linphone/SettingsFragment.java
+++ b/src/org/linphone/SettingsFragment.java
@@ -40,9 +40,6 @@ import org.linphone.tools.OpenH264DownloadHelper;
import org.linphone.ui.LedPreference;
import org.linphone.ui.PreferencesListFragment;
-import android.app.AlertDialog;
-import android.app.FragmentManager;
-import android.content.DialogInterface;
import android.Manifest;
import android.app.AlertDialog;
import android.content.Context;
diff --git a/src/org/linphone/ui/BubbleChat.java b/src/org/linphone/ui/BubbleChat.java
deleted file mode 100644
index 47a97e27f..000000000
--- a/src/org/linphone/ui/BubbleChat.java
+++ /dev/null
@@ -1,463 +0,0 @@
-package org.linphone.ui;
-/*
-BubbleChat.java
-Copyright (C) 2012 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.
-*/
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.lang.ref.WeakReference;
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
-import java.util.HashMap;
-import java.util.Map.Entry;
-
-import org.linphone.LinphoneActivity;
-import org.linphone.LinphoneContact;
-import org.linphone.LinphoneManager;
-import org.linphone.LinphoneUtils;
-import org.linphone.R;
-import org.linphone.compatibility.Compatibility;
-import org.linphone.core.LinphoneBuffer;
-import org.linphone.core.LinphoneChatMessage;
-import org.linphone.core.LinphoneChatMessage.State;
-import org.linphone.core.LinphoneContent;
-import org.linphone.mediastream.Log;
-
-import android.Manifest;
-import android.annotation.SuppressLint;
-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.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.media.ThumbnailUtils;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Environment;
-import android.provider.MediaStore;
-import android.text.Spannable;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.method.LinkMovementMethod;
-import android.text.style.ImageSpan;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.ProgressBar;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-/**
- * @author Sylvain Berfini
- */
-@SuppressLint("SimpleDateFormat")
-public class BubbleChat implements LinphoneChatMessage.LinphoneChatMessageListener {
- private static final HashMap emoticons = new HashMap();
-
- private View view;
- private ImageView statusView, contactPicture;
- private LinphoneChatMessage nativeMessage;
- private Context mContext;
- private Button cancelUpload, acceptDownload;
- private static final int SIZE_MAX = 512;
- private ProgressBar progressBar, inprogress;
- private Bitmap defaultBitmap;
-
- @SuppressLint("InflateParams")
- public BubbleChat(final Context context, LinphoneChatMessage message, LinphoneContact c) {
- if (message == null) {
- return;
- }
- nativeMessage = message;
- mContext = context;
-
- if (message.isOutgoing()) {
- view = LayoutInflater.from(context).inflate(R.layout.chat_bubble_outgoing, null);
- } else {
- view = LayoutInflater.from(context).inflate(R.layout.chat_bubble_incoming, null);
- }
- view.setId(message.getStorageId());
-
- defaultBitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.chat_picture_over);
- inprogress = (ProgressBar) view.findViewById(R.id.inprogress);
- progressBar = (ProgressBar) view.findViewById(R.id.progress_bar);
-
- LinphoneChatMessage.State status = message.getStatus();
- statusView = (ImageView) view.findViewById(R.id.status);
-
- if (statusView != null) {
- if (status == LinphoneChatMessage.State.Delivered) {
- statusView.setVisibility(View.INVISIBLE);
- inprogress.setVisibility(View.GONE);
- } else if (status == LinphoneChatMessage.State.NotDelivered) {
- statusView.setVisibility(View.VISIBLE);
- statusView.setImageResource(R.drawable.chat_message_not_delivered);
- } else {
- statusView.setVisibility(View.GONE);
- inprogress.setVisibility(View.VISIBLE);
- }
- }
-
- String externalBodyUrl = message.getExternalBodyUrl();
- LinphoneContent fileTransferContent = message.getFileTransferInformation();
-
- if(nativeMessage.isOutgoing()){
- cancelUpload = (Button) view.findViewById(R.id.cancel_upload);
- cancelUpload.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (LinphoneManager.getInstance().getMessageUploadPending() != null) {
- progressBar.setVisibility(View.GONE);
- progressBar.setProgress(0);
- nativeMessage.cancelFileTransfer();
- LinphoneManager.getInstance().setUploadPendingFileMessage(null);
- }
- }
- });
- }
-
- if(LinphoneManager.getInstance().getMessageUploadPending() != null){
- progressBar.setVisibility(View.VISIBLE);
- LinphoneManager.addListener(this);
- }
-
- if (externalBodyUrl != null || fileTransferContent != null) {
- String appData = message.getAppData();
- ImageView imageView = (ImageView) view.findViewById(R.id.image);
-
- if(nativeMessage.isOutgoing() && appData != null){
- imageView.setVisibility(View.VISIBLE);
- loadBitmap(appData, imageView);
-
- RelativeLayout imageLayout = (RelativeLayout) view.findViewById(R.id.file_transfer_layout);
- if(LinphoneManager.getInstance().getMessageUploadPending() != null && LinphoneManager.getInstance().getMessageUploadPending().getStorageId() == nativeMessage.getStorageId()){
- inprogress.setVisibility(View.INVISIBLE);
- imageLayout.setVisibility(View.VISIBLE);
- nativeMessage.setListener(LinphoneManager.getInstance());
- }
- } else {
- if (appData != null && !LinphoneManager.getInstance().isMessagePending(nativeMessage) &&
- appData.contains(context.getString(R.string.temp_photo_name_with_date).split("%s")[0])) {
- appData = null;
- }
-
- RelativeLayout imageLayout = (RelativeLayout) view.findViewById(R.id.file_transfer_layout);
- acceptDownload = (Button) view.findViewById(R.id.accept_download);
-
- if (appData == null) {
- LinphoneManager.addListener(this);
- imageLayout.setVisibility(View.VISIBLE);
- acceptDownload.setOnClickListener(new 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 extension = nativeMessage.getFileTransferInformation().getSubtype();
- String filename = context.getString(R.string.temp_photo_name_with_date).replace("%s", String.valueOf(System.currentTimeMillis())) + "." + extension;
- File file = new File(Environment.getExternalStorageDirectory(), filename);
- nativeMessage.setAppData(filename);
- LinphoneManager.getInstance().addDownloadMessagePending(nativeMessage);
- nativeMessage.setListener(LinphoneManager.getInstance());
- nativeMessage.setFileTransferFilepath(file.getPath());
- nativeMessage.downloadFile();
- } else {
- Log.w("WRITE_EXTERNAL_STORAGE permission not granted, won't be able to store the downloaded file");
- LinphoneActivity.instance().checkAndRequestExternalStoragePermission();
- }
- }
- });
- } else {
- if (LinphoneManager.getInstance().isMessagePending(nativeMessage)) {
- LinphoneManager.addListener(this);
- acceptDownload.setEnabled(false);
- imageLayout.setVisibility(View.VISIBLE);
- } else {
- LinphoneManager.removeListener(this);
- imageLayout.setVisibility(View.GONE);
- imageView.setVisibility(View.VISIBLE);
- loadBitmap(appData, imageView);
- }
- }
- }
- } else {
- TextView msgView = (TextView) view.findViewById(R.id.message);
- if (msgView != null) {
- Spanned text = null;
- String msg = message.getText();
- if (msg != null) {
- text = getSmiledText(context, getTextWithHttpLinks(msg));
- msgView.setText(text);
- msgView.setMovementMethod(LinkMovementMethod.getInstance());
- msgView.setVisibility(View.VISIBLE);
- }
- }
- }
-
- TextView contact = (TextView) view.findViewById(R.id.contact_header);
-
-
-
- contactPicture = (ImageView) view.findViewById(R.id.contact_picture);
-
- String displayName = nativeMessage.getFrom().getUserName();
- if(!nativeMessage.isOutgoing()) {
- if (c != null) {
- displayName = c.getFullName();
- LinphoneUtils.setImagePictureFromUri(view.getContext(), contactPicture, c.getPhotoUri(), c.getThumbnailUri());
- } else {
- contactPicture.setImageResource(R.drawable.avatar);
- }
- }
-
- contact.setText(timestampToHumanDate(context, message.getTime()) + " - " + displayName);
-
- }
-
- public View getView() {
- return view;
- }
-
- public void destroy() {
- defaultBitmap.recycle();
- }
-
- private String timestampToHumanDate(Context context, long timestamp) {
- try {
- Calendar cal = Calendar.getInstance();
- cal.setTimeInMillis(timestamp);
-
- SimpleDateFormat dateFormat;
- if (isToday(cal)) {
- dateFormat = new SimpleDateFormat(context.getResources().getString(R.string.today_date_format));
- } else {
- dateFormat = new SimpleDateFormat(context.getResources().getString(R.string.messages_date_format));
- }
-
- return dateFormat.format(cal.getTime());
- } catch (NumberFormatException nfe) {
- return String.valueOf(timestamp);
- }
- }
-
- private boolean isToday(Calendar cal) {
- return isSameDay(cal, Calendar.getInstance());
- }
-
- private boolean isSameDay(Calendar cal1, Calendar cal2) {
- if (cal1 == null || cal2 == null) {
- return false;
- }
-
- return (cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA) &&
- cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
- cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR));
- }
-
- public static Spannable getSmiledText(Context context, Spanned spanned) {
- SpannableStringBuilder builder = new SpannableStringBuilder(spanned);
- String text = spanned.toString();
-
- for (Entry entry : emoticons.entrySet()) {
- String key = entry.getKey();
- int indexOf = text.indexOf(key);
- while (indexOf >= 0) {
- int end = indexOf + key.length();
- builder.setSpan(new ImageSpan(context, entry.getValue()), indexOf, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- indexOf = text.indexOf(key, end);
- }
- }
-
- return builder;
- }
-
- public static Spanned getTextWithHttpLinks(String text) {
- if (text.contains("<")) {
- text = text.replace("<", "<");
- }
- if (text.contains(">")) {
- text = text.replace(">", ">");
- }
- if (text.contains("http://")) {
- int indexHttp = text.indexOf("http://");
- int indexFinHttp = text.indexOf(" ", indexHttp) == -1 ? text.length() : text.indexOf(" ", indexHttp);
- String link = text.substring(indexHttp, indexFinHttp);
- String linkWithoutScheme = link.replace("http://", "");
- text = text.replaceFirst(link, "" + linkWithoutScheme + "");
- }
- if (text.contains("https://")) {
- int indexHttp = text.indexOf("https://");
- int indexFinHttp = text.indexOf(" ", indexHttp) == -1 ? text.length() : text.indexOf(" ", indexHttp);
- String link = text.substring(indexHttp, indexFinHttp);
- String linkWithoutScheme = link.replace("https://", "");
- text = text.replaceFirst(link, "" + linkWithoutScheme + "");
- }
-
- return Compatibility.fromHtml(text);
- }
-
- public String getTextMessage() {
- return nativeMessage.getText();
- }
-
- public State getStatus() {
- return nativeMessage.getStatus();
- }
-
- public LinphoneChatMessage getNativeMessageObject() {
- return nativeMessage;
- }
-
- public int getId() {
- return nativeMessage.getStorageId();
- }
-
- public void loadBitmap(String path, ImageView imageView) {
- if (cancelPotentialWork(path, imageView)) {
- BitmapWorkerTask task = new BitmapWorkerTask(imageView);
- final AsyncBitmap asyncBitmap = new AsyncBitmap(mContext.getResources(), defaultBitmap, task);
- imageView.setImageDrawable(asyncBitmap);
- task.execute(path);
- }
- }
-
- 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);
- }
-
- // Decode image in background.
- @Override
- protected Bitmap doInBackground(String... params) {
- path = params[0];
- Bitmap bm = null;
-
- 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);
- path = "file://" + path;
- }
-
- if (bm != null) {
- bm = ThumbnailUtils.extractThumbnail(bm, SIZE_MAX, SIZE_MAX);
- }
- return bm;
- }
-
- // Once complete, see if ImageView is still around and set bitmap.
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- if (isCancelled()) {
- 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 OnClickListener() {
- @Override
- public void onClick(View v) {
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setDataAndType(Uri.parse((String)v.getTag()), "image/*");
- mContext.startActivity(intent);
- }
- });
- }
- }
- }
- }
-
- static 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();
- }
- }
-
- public static 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 static 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;
- }
-
- @Override
- public void onLinphoneChatMessageStateChanged(LinphoneChatMessage msg, State state) {
- }
-
- @Override
- public void onLinphoneChatMessageFileTransferReceived(LinphoneChatMessage msg, LinphoneContent content, LinphoneBuffer buffer) {
- }
-
- @Override
- public void onLinphoneChatMessageFileTransferSent(LinphoneChatMessage msg, LinphoneContent content, int offset, int size, LinphoneBuffer bufferToFill) {
- }
-
- @Override
- public void onLinphoneChatMessageFileTransferProgressChanged(LinphoneChatMessage msg, LinphoneContent content, int offset, int total) {
- if(nativeMessage.getStorageId() == msg.getStorageId())
- progressBar.setProgress(offset * 100 / total);
- }
-}