co.taqat.call.ChatFragment.java Source code

Java tutorial

Introduction

Here is the source code for co.taqat.call.ChatFragment.java

Source

package co.taqat.call;
/*
ChatFragment.java
Copyright (C) 2015  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.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;

import co.taqat.call.compatibility.Compatibility;
import org.linphone.core.LinphoneAddress;
import org.linphone.core.LinphoneBuffer;
import org.linphone.core.LinphoneChatMessage;
import org.linphone.core.LinphoneChatMessage.State;
import org.linphone.core.LinphoneChatRoom;
import org.linphone.core.LinphoneContent;
import org.linphone.core.LinphoneCore;
import org.linphone.core.LinphoneCoreFactory;
import org.linphone.core.LinphoneCoreListenerBase;
import org.linphone.mediastream.Log;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
import android.app.Fragment;
import android.app.ProgressDialog;
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;
import android.os.Environment;
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.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
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;

public class ChatFragment extends Fragment
        implements OnClickListener, LinphoneChatMessage.LinphoneChatMessageListener {
    private static final int ADD_PHOTO = 1337;
    private static final int MENU_DELETE_MESSAGE = 0;
    private static final int MENU_PICTURE_SMALL = 2;
    private static final int MENU_PICTURE_MEDIUM = 3;
    private static final int MENU_PICTURE_LARGE = 4;
    private static final int MENU_PICTURE_REAL = 5;
    private static final int MENU_COPY_TEXT = 6;
    private static final int MENU_RESEND_MESSAGE = 7;
    private static final int SIZE_SMALL = 500;
    private static final int SIZE_MEDIUM = 1000;
    private static final int SIZE_LARGE = 1500;
    private static final int SIZE_MAX = 2048;

    private LinphoneChatRoom chatRoom;
    private String sipUri;
    private EditText message;
    private ImageView edit, selectAll, deselectAll, startCall, delete, sendImage, sendMessage, cancel;
    private TextView contactName, remoteComposing;
    private ImageView back, backToCall;
    private EditText searchContactField;
    private LinearLayout topBar, editList;
    private SearchContactsListAdapter searchAdapter;
    private ListView messagesList, resultContactsSearch;
    private LayoutInflater inflater;
    private Bitmap defaultBitmap;

    private boolean isEditMode = false;
    private LinphoneContact contact;
    private Uri imageToUploadUri;
    private String filePathToUpload;
    private TextWatcher textWatcher;
    private ViewTreeObserver.OnGlobalLayoutListener keyboardListener;
    private ChatMessageAdapter adapter;

    private LinphoneCoreListenerBase mListener;
    private ByteArrayInputStream mUploadingImageStream;
    private boolean newChatConversation = false;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        final View view = inflater.inflate(R.layout.chat, container, false);

        LinphoneManager.addListener(this);
        // Retain the fragment across configuration changes
        setRetainInstance(true);

        this.inflater = inflater;

        if (getArguments() == null || getArguments().getString("SipUri") == null) {
            newChatConversation = true;
        } else {
            //Retrieve parameter from intent
            sipUri = getArguments().getString("SipUri");
        }

        //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);
        searchContactField = (EditText) view.findViewById(R.id.search_contact_field);
        resultContactsSearch = (ListView) view.findViewById(R.id.result_contacts);

        editList = (LinearLayout) view.findViewById(R.id.edit_list);
        topBar = (LinearLayout) view.findViewById(R.id.top_bar);

        sendMessage = (ImageView) view.findViewById(R.id.send_message);
        sendMessage.setOnClickListener(this);

        remoteComposing = (TextView) view.findViewById(R.id.remote_composing);
        remoteComposing.setVisibility(View.GONE);

        cancel = (ImageView) view.findViewById(R.id.cancel);
        cancel.setOnClickListener(this);

        edit = (ImageView) view.findViewById(R.id.edit);
        edit.setOnClickListener(this);

        startCall = (ImageView) view.findViewById(R.id.start_call);
        startCall.setOnClickListener(this);

        backToCall = (ImageView) view.findViewById(R.id.back_to_call);
        backToCall.setOnClickListener(this);

        selectAll = (ImageView) view.findViewById(R.id.select_all);
        selectAll.setOnClickListener(this);

        deselectAll = (ImageView) view.findViewById(R.id.deselect_all);
        deselectAll.setOnClickListener(this);

        delete = (ImageView) view.findViewById(R.id.delete);
        delete.setOnClickListener(this);

        if (newChatConversation) {
            initNewChatConversation();
        }

        message = (EditText) view.findViewById(R.id.message);

        sendImage = (ImageView) view.findViewById(R.id.send_picture);
        if (!getResources().getBoolean(R.bool.disable_chat_send_file)) {
            sendImage.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    pickImage();
                    LinphoneActivity.instance().checkAndRequestPermissionsToSendImage();
                }
            });
            //registerForContextMenu(sendImage);
        } else {
            sendImage.setEnabled(false);
        }

        back = (ImageView) view.findViewById(R.id.back);
        if (getResources().getBoolean(R.bool.isTablet)) {
            back.setVisibility(View.INVISIBLE);
        } else {
            back.setOnClickListener(this);
        }

        mListener = new LinphoneCoreListenerBase() {
            @Override
            public void messageReceived(LinphoneCore lc, LinphoneChatRoom cr, LinphoneChatMessage message) {
                LinphoneAddress from = cr.getPeerAddress();
                if (from.asStringUriOnly().equals(sipUri)) {
                    LinphoneService.instance().removeMessageNotification();
                    cr.markAsRead();
                    LinphoneActivity.instance().updateMissedChatCount();
                    adapter.addMessage(cr.getHistory(1)[0]);

                    String externalBodyUrl = message.getExternalBodyUrl();
                    LinphoneContent fileTransferContent = message.getFileTransferInformation();
                    if (externalBodyUrl != null || fileTransferContent != null) {
                        LinphoneActivity.instance().checkAndRequestExternalStoragePermission();
                    }
                }
            }

            @Override
            public void isComposingReceived(LinphoneCore lc, LinphoneChatRoom room) {
                if (chatRoom != null && room != null && chatRoom.getPeerAddress().asStringUriOnly()
                        .equals(room.getPeerAddress().asStringUriOnly())) {
                    remoteComposing.setVisibility(chatRoom.isRemoteComposing() ? View.VISIBLE : View.GONE);
                }
            }
        };

        textWatcher = new TextWatcher() {
            public void afterTextChanged(Editable arg0) {
            }

            public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
            }

            public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
                if (message.getText().toString().equals("")) {
                    sendMessage.setEnabled(false);
                } else {
                    if (chatRoom != null)
                        chatRoom.compose();
                    sendMessage.setEnabled(true);
                }
            }
        };

        return view;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        if (message != null) {
            outState.putString("messageDraft", message.getText().toString());
        }
        if (contact != null) {
            outState.putSerializable("contactDraft", contact);
            outState.putString("sipUriDraft", sipUri);
        }
        super.onSaveInstanceState(outState);
    }

    private void addVirtualKeyboardVisiblityListener() {
        keyboardListener = new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                Rect visibleArea = new Rect();
                getActivity().getWindow().getDecorView().getWindowVisibleDisplayFrame(visibleArea);

                int heightDiff = getActivity().getWindow().getDecorView().getRootView().getHeight()
                        - (visibleArea.bottom - visibleArea.top);
                if (heightDiff > 200) {
                    showKeyboardVisibleMode();
                } else {
                    hideKeyboardVisibleMode();
                }
            }
        };
        getActivity().getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(keyboardListener);
    }

    private void removeVirtualKeyboardVisiblityListener() {
        Compatibility.removeGlobalLayoutListener(getActivity().getWindow().getDecorView().getViewTreeObserver(),
                keyboardListener);
    }

    public void showKeyboardVisibleMode() {
        boolean isOrientationLandscape = getResources()
                .getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
        if (isOrientationLandscape && topBar != null) {
            //topBar.setVisibility(View.GONE);
        }
        LinphoneActivity.instance().hideTabBar(true);
        //contactPicture.setVisibility(View.GONE);
    }

    public void hideKeyboardVisibleMode() {
        boolean isOrientationLandscape = getResources()
                .getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
        //contactPicture.setVisibility(View.VISIBLE);
        if (isOrientationLandscape && topBar != null) {
            //topBar.setVisibility(View.VISIBLE);
        }
        LinphoneActivity.instance().hideTabBar(false);
    }

    public int getNbItemsChecked() {
        int size = messagesList.getAdapter().getCount();
        int nb = 0;
        for (int i = 0; i < size; i++) {
            if (messagesList.isItemChecked(i)) {
                nb++;
            }
        }
        return nb;
    }

    public void enabledDeleteButton(Boolean enabled) {
        if (enabled) {
            delete.setEnabled(true);
        } else {
            if (getNbItemsChecked() == 0) {
                delete.setEnabled(false);
            }
        }
    }

    public void initChatRoom(String sipUri) {
        LinphoneCore lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();

        LinphoneAddress lAddress = null;
        if (sipUri == null) {
            contact = null; // Tablet rotation issue
            initNewChatConversation();
        } else {
            try {
                lAddress = lc.interpretUrl(sipUri);
            } catch (Exception e) {
                //TODO Error popup and quit chat
            }

            if (lAddress != null) {
                chatRoom = lc.getChatRoom(lAddress);
                chatRoom.markAsRead();
                LinphoneActivity.instance().updateMissedChatCount();
                contact = ContactsManager.getInstance().findContactFromAddress(lAddress);
                if (chatRoom != null) {
                    searchContactField.setVisibility(View.GONE);
                    resultContactsSearch.setVisibility(View.GONE);
                    displayChatHeader(lAddress);
                    displayMessageList();
                }
            }
        }
    }

    private void redrawMessageList() {
        if (adapter != null) {
            adapter.notifyDataSetChanged();
        }
    }

    private void displayMessageList() {
        if (chatRoom != null) {
            if (adapter != null) {
                adapter.refreshHistory();
            } else {
                adapter = new ChatMessageAdapter(getActivity());
            }
        }
        messagesList.setAdapter(adapter);
        messagesList.setVisibility(ListView.VISIBLE);
    }

    private void displayChatHeader(LinphoneAddress address) {
        if (contact != null || address != null) {
            if (contact != null) {
                contactName.setText(contact.getFullName());
            } else {
                contactName.setText(LinphoneUtils.getAddressDisplayName(address));
            }
            topBar.setVisibility(View.VISIBLE);
            edit.setVisibility(View.VISIBLE);
            contactName.setVisibility(View.VISIBLE);
        }
    }

    public void changeDisplayedChat(String newSipUri, String displayName, String pictureUri) {
        this.sipUri = newSipUri;
        initChatRoom(sipUri);
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
        if (v.getId() == R.id.send_picture) {
            menu.add(0, MENU_PICTURE_SMALL, 0, getString(R.string.share_picture_size_small));
            menu.add(0, MENU_PICTURE_MEDIUM, 0, getString(R.string.share_picture_size_medium));
            menu.add(0, MENU_PICTURE_LARGE, 0, getString(R.string.share_picture_size_large));
            //         Not a good idea, very big pictures cause Out of Memory exceptions, slow display, ...
            //         menu.add(0, MENU_PICTURE_REAL, 0, getString(R.string.share_picture_size_real));
        } else {
            menu.add(v.getId(), MENU_DELETE_MESSAGE, 0, getString(R.string.delete));
            menu.add(v.getId(), MENU_COPY_TEXT, 0, getString(R.string.copy_text));
        }

        LinphoneChatMessage msg = getMessageForId(v.getId());
        if (msg != null && msg.getStatus() == LinphoneChatMessage.State.NotDelivered) {
            menu.add(v.getId(), MENU_RESEND_MESSAGE, 0, getString(R.string.retry));
        }
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case MENU_DELETE_MESSAGE:
            if (chatRoom != null) {
                LinphoneChatMessage message = getMessageForId(item.getGroupId());
                if (message != null) {
                    chatRoom.deleteMessage(message);
                    invalidate();
                }
            }
            break;
        case MENU_COPY_TEXT:
            copyTextMessageToClipboard(item.getGroupId());
            break;
        case MENU_RESEND_MESSAGE:
            resendMessage(item.getGroupId());
            break;
        case MENU_PICTURE_SMALL:
            sendImageMessage(filePathToUpload, SIZE_SMALL);
            break;
        case MENU_PICTURE_MEDIUM:
            sendImageMessage(filePathToUpload, SIZE_MEDIUM);
            break;
        case MENU_PICTURE_LARGE:
            sendImageMessage(filePathToUpload, SIZE_LARGE);
            break;
        case MENU_PICTURE_REAL:
            sendImageMessage(filePathToUpload, SIZE_MAX);
            break;
        }
        return true;
    }

    @Override
    public void onPause() {
        message.removeTextChangedListener(textWatcher);
        removeVirtualKeyboardVisiblityListener();

        LinphoneService.instance().removeMessageNotification();

        LinphoneCore lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
        if (lc != null) {
            lc.removeListener(mListener);
        }

        LinphoneManager.removeListener(this);
        onSaveInstanceState(getArguments());

        //Hide keybord
        InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(message.getWindowToken(), 0);

        super.onPause();
    }

    @Override
    public void onDestroy() {
        if (adapter != null) {
            adapter.destroy();
        }
        if (defaultBitmap != null) {
            defaultBitmap.recycle();
            defaultBitmap = null;
        }
        super.onDestroy();
    }

    @SuppressLint("UseSparseArrays")
    @Override
    public void onResume() {
        super.onResume();

        message.addTextChangedListener(textWatcher);
        addVirtualKeyboardVisiblityListener();

        LinphoneCore lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
        if (lc != null) {
            lc.addListener(mListener);
        }

        if (LinphoneActivity.isInstanciated()) {
            LinphoneActivity.instance().selectMenu(FragmentsAvailable.CHAT);
        }

        if (LinphoneManager.getLc().isIncall()) {
            backToCall.setVisibility(View.VISIBLE);
            startCall.setVisibility(View.GONE);
        } else {
            if (!newChatConversation) {
                backToCall.setVisibility(View.GONE);
                startCall.setVisibility(View.VISIBLE);
            }
        }

        LinphoneManager.addListener(this);

        // Force hide keyboard
        getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN
                | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);

        String draft = getArguments().getString("messageDraft");
        message.setText(draft);
        contact = (LinphoneContact) getArguments().getSerializable("contactDraft");
        if (contact != null) {
            contactName.setText(contact.getFullName());
            sipUri = getArguments().getString("sipUriDraft");
            getArguments().clear();
        }

        if (!newChatConversation || contact != null) {
            initChatRoom(sipUri);
            searchContactField.setVisibility(View.GONE);
            resultContactsSearch.setVisibility(View.GONE);
            remoteComposing.setVisibility(chatRoom.isRemoteComposing() ? View.VISIBLE : View.GONE);
        }
    }

    private void selectAllList(boolean isSelectAll) {
        int size = messagesList.getAdapter().getCount();
        for (int i = 0; i < size; i++) {
            messagesList.setItemChecked(i, isSelectAll);
        }
    }

    public void quitEditMode() {
        isEditMode = false;
        editList.setVisibility(View.GONE);
        topBar.setVisibility(View.VISIBLE);
        redrawMessageList();
    }

    private void removeChats() {
        int size = messagesList.getAdapter().getCount();
        for (int i = 0; i < size; i++) {
            if (messagesList.isItemChecked(i)) {
                LinphoneChatMessage message = (LinphoneChatMessage) messagesList.getAdapter().getItem(i);
                chatRoom.deleteMessage(message);
            }
        }
        invalidate();
    }

    @Override
    public void onClick(View v) {
        int id = v.getId();

        if (id == R.id.back_to_call) {
            LinphoneActivity.instance().resetClassicMenuLayoutAndGoBackToCallIfStillRunning();
            return;
        }
        if (id == R.id.select_all) {
            deselectAll.setVisibility(View.VISIBLE);
            selectAll.setVisibility(View.GONE);
            enabledDeleteButton(true);
            selectAllList(true);
            return;
        }
        if (id == R.id.deselect_all) {
            deselectAll.setVisibility(View.GONE);
            selectAll.setVisibility(View.VISIBLE);
            enabledDeleteButton(false);
            selectAllList(false);
            return;
        }
        if (id == R.id.cancel) {
            quitEditMode();
            return;
        }
        if (id == R.id.delete) {
            final Dialog dialog = LinphoneActivity.instance().displayDialog(getString(R.string.delete_text));
            Button delete = (Button) dialog.findViewById(R.id.delete_button);
            Button cancel = (Button) dialog.findViewById(R.id.cancel);

            delete.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View view) {
                    removeChats();
                    dialog.dismiss();
                    quitEditMode();
                }
            });

            cancel.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View view) {
                    dialog.dismiss();
                    quitEditMode();
                }
            });
            dialog.show();
            return;
        }
        if (id == R.id.send_message) {
            sendTextMessage();
        }
        if (id == R.id.edit) {
            topBar.setVisibility(View.INVISIBLE);
            editList.setVisibility(View.VISIBLE);
            isEditMode = true;
            redrawMessageList();
        }
        if (id == R.id.start_call) {
            LinphoneActivity.instance().setAddresGoToDialerAndCall(sipUri,
                    LinphoneUtils.getUsernameFromAddress(sipUri), null);
        }
        if (id == R.id.back) {
            getFragmentManager().popBackStackImmediate();
        }
    }

    private void sendTextMessage() {
        sendTextMessage(message.getText().toString());
        message.setText("");
    }

    private void sendTextMessage(String messageToSend) {
        LinphoneCore lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
        boolean isNetworkReachable = lc == null ? false : lc.isNetworkReachable();
        LinphoneAddress lAddress = null;

        //Start new conversation in fast chat
        if (newChatConversation && chatRoom == null) {
            String address = searchContactField.getText().toString().toLowerCase(Locale.getDefault());
            if (address != null && !address.equals("")) {
                initChatRoom(address);
            }
        }
        if (chatRoom != null && messageToSend != null && messageToSend.length() > 0 && isNetworkReachable) {
            LinphoneChatMessage message = chatRoom.createLinphoneChatMessage(messageToSend);
            chatRoom.sendChatMessage(message);
            lAddress = chatRoom.getPeerAddress();

            if (LinphoneActivity.isInstanciated()) {
                LinphoneActivity.instance().onMessageSent(sipUri, messageToSend);
            }

            message.setListener(LinphoneManager.getInstance());
            if (newChatConversation) {
                exitNewConversationMode(lAddress.asStringUriOnly());
            } else {
                adapter.addMessage(message);
            }

            Log.i("Sent message current status: " + message.getStatus());
        } else if (!isNetworkReachable && LinphoneActivity.isInstanciated()) {
            LinphoneActivity.instance().displayCustomToast(getString(R.string.error_network_unreachable),
                    Toast.LENGTH_LONG);
        }
    }

    private void sendImageMessage(String path, int imageSize) {
        LinphoneCore lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
        boolean isNetworkReachable = lc == null ? false : lc.isNetworkReachable();

        if (newChatConversation && chatRoom == null) {
            String address = searchContactField.getText().toString();
            if (address != null && !address.equals("")) {
                initChatRoom(address);
            }
        }

        if (chatRoom != null && path != null && path.length() > 0 && isNetworkReachable) {
            try {
                Bitmap bm = BitmapFactory.decodeFile(path);
                if (bm != null) {
                    FileUploadPrepareTask task = new FileUploadPrepareTask(getActivity(), path, imageSize);
                    task.execute(bm);
                } else {
                    Log.e("Error, bitmap factory can't read " + path);
                }
            } catch (RuntimeException e) {
                Log.e("Error, not enough memory to create the bitmap");
            }
        } else if (!isNetworkReachable && LinphoneActivity.isInstanciated()) {
            LinphoneActivity.instance().displayCustomToast(getString(R.string.error_network_unreachable),
                    Toast.LENGTH_LONG);
        }
    }

    private LinphoneChatMessage getMessageForId(int id) {
        if (adapter == null)
            return null;
        for (int i = 0; i < adapter.getCount(); i++) {
            LinphoneChatMessage message = adapter.getItem(i);
            if (message.getStorageId() == id) {
                return message;
            }
        }
        return null;
    }

    private void invalidate() {
        adapter.refreshHistory();
        chatRoom.markAsRead();
    }

    private void resendMessage(int id) {
        LinphoneChatMessage message = getMessageForId(id);
        if (message == null)
            return;

        chatRoom.deleteMessage(getMessageForId(id));
        invalidate();

        if (message.getText() != null && message.getText().length() > 0) {
            sendTextMessage(message.getText());
        } else {
            sendImageMessage(message.getAppData(), 0);
        }
    }

    private void copyTextMessageToClipboard(int id) {
        LinphoneChatMessage message = null;
        for (int i = 0; i < adapter.getCount(); i++) {
            LinphoneChatMessage msg = adapter.getItem(i);
            if (msg.getStorageId() == id) {
                message = msg;
                break;
            }
        }

        String txt = null;
        if (message != null) {
            txt = message.getText();
        }
        if (txt != null) {
            ClipboardManager clipboard = (ClipboardManager) getActivity()
                    .getSystemService(Context.CLIPBOARD_SERVICE);
            ClipData clip = android.content.ClipData.newPlainText("Message", txt);
            clipboard.setPrimaryClip(clip);
            LinphoneActivity.instance().displayCustomToast(getString(R.string.text_copied_to_clipboard),
                    Toast.LENGTH_SHORT);
        }
    }

    //File transfer
    private void pickImage() {
        List<Intent> cameraIntents = new ArrayList<Intent>();
        Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        File file = new File(Environment.getExternalStorageDirectory(),
                getString(R.string.temp_photo_name_with_date).replace("%s",
                        String.valueOf(System.currentTimeMillis())));
        imageToUploadUri = Uri.fromFile(file);
        captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageToUploadUri);
        cameraIntents.add(captureIntent);

        Intent galleryIntent = new Intent();
        galleryIntent.setType("image/*");
        galleryIntent.setAction(Intent.ACTION_PICK);

        Intent chooserIntent = Intent.createChooser(galleryIntent, getString(R.string.image_picker_title));
        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, cameraIntents.toArray(new Parcelable[] {}));

        startActivityForResult(chooserIntent, ADD_PHOTO);
    }

    public String getRealPathFromURI(Uri contentUri) {
        String[] proj = { MediaStore.Images.Media.DATA };
        CursorLoader loader = new CursorLoader(getActivity(), contentUri, proj, null, null, null);
        Cursor cursor = loader.loadInBackground();
        if (cursor != null && cursor.moveToFirst()) {
            int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
            String result = cursor.getString(column_index);
            cursor.close();
            return result;
        }
        return null;
    }

    class FileUploadPrepareTask extends AsyncTask<Bitmap, Void, byte[]> {
        private String path;
        private ProgressDialog progressDialog;

        public FileUploadPrepareTask(Context context, String fileToUploadPath, int size) {
            path = fileToUploadPath;

            progressDialog = new ProgressDialog(context);
            progressDialog.setIndeterminate(true);
            progressDialog.setMessage(getString(R.string.processing_image));
            progressDialog.show();
        }

        @Override
        protected byte[] doInBackground(Bitmap... params) {
            Bitmap bm = params[0];

            if (bm.getWidth() >= bm.getHeight() && bm.getWidth() > SIZE_MAX) {
                bm = Bitmap.createScaledBitmap(bm, SIZE_MAX, (SIZE_MAX * bm.getHeight()) / bm.getWidth(), false);
            } else if (bm.getHeight() >= bm.getWidth() && bm.getHeight() > SIZE_MAX) {
                bm = Bitmap.createScaledBitmap(bm, (SIZE_MAX * bm.getWidth()) / bm.getHeight(), SIZE_MAX, false);
            }

            // Rotate the bitmap if possible/needed, using EXIF data
            try {
                if (path != null) {
                    ExifInterface exif = new ExifInterface(path);
                    int pictureOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0);
                    Matrix matrix = new Matrix();
                    if (pictureOrientation == 6) {
                        matrix.postRotate(90);
                    } else if (pictureOrientation == 3) {
                        matrix.postRotate(180);
                    } else if (pictureOrientation == 8) {
                        matrix.postRotate(270);
                    }
                    bm = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true);
                }
            } catch (Exception e) {
                Log.e(e);
            }

            ByteArrayOutputStream stream = new ByteArrayOutputStream();
            String extension = LinphoneUtils.getExtensionFromFileName(path);
            if (extension != null && extension.toLowerCase(Locale.getDefault()).equals("png")) {
                bm.compress(Bitmap.CompressFormat.PNG, 100, stream);
            } else {
                bm.compress(Bitmap.CompressFormat.JPEG, 100, stream);
            }
            byte[] byteArray = stream.toByteArray();
            return byteArray;
        }

        @Override
        protected void onPostExecute(byte[] result) {
            if (progressDialog != null && progressDialog.isShowing()) {
                progressDialog.dismiss();
            }
            mUploadingImageStream = new ByteArrayInputStream(result);

            String fileName = path.substring(path.lastIndexOf("/") + 1);
            String extension = LinphoneUtils.getExtensionFromFileName(fileName);
            LinphoneContent content = LinphoneCoreFactory.instance().createLinphoneContent("image", extension,
                    result, null);
            content.setName(fileName);

            LinphoneChatMessage message = chatRoom.createFileTransferMessage(content);
            message.setListener(LinphoneManager.getInstance());
            message.setAppData(path);

            LinphoneManager.getInstance().setUploadPendingFileMessage(message);
            LinphoneManager.getInstance().setUploadingImageStream(mUploadingImageStream);

            chatRoom.sendChatMessage(message);
            adapter.addMessage(message);
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == ADD_PHOTO && resultCode == Activity.RESULT_OK) {
            String fileToUploadPath = null;

            if (data != null && data.getData() != null) {
                fileToUploadPath = getRealPathFromURI(data.getData());
            } else if (imageToUploadUri != null) {
                fileToUploadPath = imageToUploadUri.getPath();
            }

            if (fileToUploadPath != null) {
                //showPopupMenuAskingImageSize(fileToUploadPath);
                sendImageMessage(fileToUploadPath, 0);
            }
        } else {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }

    //New conversation
    private void exitNewConversationMode(String address) {
        sipUri = address;
        searchContactField.setVisibility(View.GONE);
        resultContactsSearch.setVisibility(View.GONE);
        messagesList.setVisibility(View.VISIBLE);
        contactName.setVisibility(View.VISIBLE);
        edit.setVisibility(View.VISIBLE);
        startCall.setVisibility(View.VISIBLE);

        if (getResources().getBoolean(R.bool.isTablet)) {
            back.setVisibility(View.INVISIBLE);
        } else {
            back.setOnClickListener(this);
        }

        newChatConversation = false;
        initChatRoom(sipUri);
    }

    private void initNewChatConversation() {
        messagesList.setVisibility(View.GONE);
        edit.setVisibility(View.INVISIBLE);
        startCall.setVisibility(View.INVISIBLE);
        contactName.setVisibility(View.INVISIBLE);
        resultContactsSearch.setVisibility(View.VISIBLE);
        searchAdapter = new SearchContactsListAdapter(null);
        resultContactsSearch.setAdapter(searchAdapter);
        searchContactField.setVisibility(View.VISIBLE);
        searchContactField.requestFocus();
        searchContactField.addTextChangedListener(new TextWatcher() {
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
            }

            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }

            @Override
            public void afterTextChanged(Editable s) {
                searchContacts(searchContactField.getText().toString());
            }
        });
    }

    private class ContactAddress {
        LinphoneContact contact;
        String address;

        private ContactAddress(LinphoneContact c, String a) {
            this.contact = c;
            this.address = a;
        }
    }

    private void searchContacts(String search) {
        if (search == null || search.length() == 0) {
            resultContactsSearch.setAdapter(new SearchContactsListAdapter(null));
            return;
        }

        List<ContactAddress> result = new ArrayList<ContactAddress>();
        if (search != null) {
            for (ContactAddress c : searchAdapter.contacts) {
                String address = c.address;
                if (address.startsWith("sip:"))
                    address = address.substring(4);
                if (c.contact.getFullName() != null
                        && c.contact.getFullName().toLowerCase(Locale.getDefault())
                                .startsWith(search.toLowerCase(Locale.getDefault()))
                        || address.toLowerCase(Locale.getDefault())
                                .startsWith(search.toLowerCase(Locale.getDefault()))) {
                    result.add(c);
                }
            }
        }

        resultContactsSearch.setAdapter(new SearchContactsListAdapter(result));
        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 ImageView contactPictureMask;

            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);
                contactPictureMask = (ImageView) view.findViewById(R.id.mask);
            }

            @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<LinphoneChatMessage> history;
        Context context;

        public ChatMessageAdapter(Context c) {
            context = c;
            history = new ArrayList<LinphoneChatMessage>();
            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();
            messagesList.setSelection(getCount() - 1);
        }

        @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;
            boolean sameMessage = false;

            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);
            }

            if (holder.id == message.getStorageId()) {
                // Horrible workaround to not reload image on edit chat list
                if (holder.messageImage.getTag() != null && (holder.messageImage.getTag()
                        .equals(message.getAppData())
                        || ((String) holder.messageImage.getTag()).substring(7).equals(message.getAppData()))) {
                    sameMessage = true;
                }
            } else {
                holder.id = message.getStorageId();
            }
            view.setId(holder.id);
            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().getDisplayName();
            if (displayName == null) {
                displayName = message.getFrom().getUserName();
            }
            if (!message.isOutgoing()) {
                if (contact != null) {
                    if (contact != null && contact.getFullName() != 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) {
                String appData = message.getAppData();

                if (message.isOutgoing() && appData != null) {
                    holder.messageImage.setVisibility(View.VISIBLE);
                    if (!sameMessage) {
                        loadBitmap(message.getAppData(), holder.messageImage);
                        holder.messageImage.setTag(message.getAppData());
                    }

                    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);
                            if (!sameMessage) {
                                loadBitmap(appData, holder.messageImage);
                                holder.messageImage.setTag(message.getAppData());
                            }
                        }
                    }
                }
            } 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);
                Compatibility.setTextAppearance(holder.contactName, getActivity(), R.style.font3);
                Compatibility.setTextAppearance(holder.fileTransferAction, getActivity(), R.style.font15);
                holder.fileTransferAction.setBackgroundResource(R.drawable.resizable_confirm_delete_button);
                holder.contactPictureMask.setImageResource(R.drawable.avatar_chat_mask_outgoing);
            } 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);
                Compatibility.setTextAppearance(holder.contactName, getActivity(), R.style.font9);
                Compatibility.setTextAppearance(holder.fileTransferAction, getActivity(), R.style.font8);
                holder.fileTransferAction.setBackgroundResource(R.drawable.resizable_assistant_button);
                holder.contactPictureMask.setImageResource(R.drawable.avatar_chat_mask);
            }
            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("<", "&lt;");
            }
            if (text.contains(">")) {
                text = text.replace(">", "&gt;");
            }
            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, "<a href=\"" + link + "\">" + linkWithoutScheme + "</a>");
            }
            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, "<a href=\"" + link + "\">" + linkWithoutScheme + "</a>");
            }

            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<String, Void, Bitmap> {
            private final WeakReference<ImageView> 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>(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<BitmapWorkerTask> bitmapWorkerTaskReference;

            public AsyncBitmap(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
                super(res, bitmap);
                bitmapWorkerTaskReference = new WeakReference<BitmapWorkerTask>(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;
            public TextView address;

            public ViewHolder(View view) {
                name = (TextView) view.findViewById(R.id.contact_name);
                address = (TextView) view.findViewById(R.id.contact_address);
            }
        }

        private List<ContactAddress> contacts;
        private LayoutInflater mInflater;

        SearchContactsListAdapter(List<ContactAddress> contactsList) {
            mInflater = inflater;
            if (contactsList == null) {
                contacts = getContactsList();
            } else {
                contacts = contactsList;
            }
        }

        public List<ContactAddress> getContactsList() {
            List<ContactAddress> list = new ArrayList<ContactAddress>();
            if (ContactsManager.getInstance().hasContacts()) {
                for (LinphoneContact con : ContactsManager.getInstance().getContacts()) {
                    for (LinphoneNumberOrAddress noa : con.getNumbersOrAddresses()) {
                        String value = noa.getValue();
                        // Fix for sip:username compatibility issue
                        if (value.startsWith("sip:") && !value.contains("@")) {
                            value = value.substring(4);
                            value = LinphoneUtils.getFullAddressFromUsername(value);
                        }
                        list.add(new ContactAddress(con, value));
                    }
                }
            }
            return list;
        }

        public int getCount() {
            return contacts.size();
        }

        public ContactAddress getItem(int position) {
            if (contacts == null || position >= contacts.size()) {
                contacts = getContactsList();
                return contacts.get(position);
            } else {
                return contacts.get(position);
            }
        }

        public long getItemId(int position) {
            return position;
        }

        public View getView(int position, View convertView, ViewGroup parent) {
            View view = null;
            ContactAddress contact;
            ViewHolder holder = null;

            do {
                contact = getItem(position);
            } while (contact == null);

            if (convertView != null) {
                view = convertView;
                holder = (ViewHolder) view.getTag();
            } else {
                view = mInflater.inflate(R.layout.search_contact_cell, parent, false);
                holder = new ViewHolder(view);
                view.setTag(holder);
            }

            final String a = contact.address;
            LinphoneContact c = contact.contact;

            holder.name.setText(c.getFullName());
            holder.address.setText(a);

            view.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View view) {
                    exitNewConversationMode(a);
                }
            });

            return view;
        }
    }

    //LinphoneChatMessage Listener
    @Override
    public void onLinphoneChatMessageStateChanged(LinphoneChatMessage msg, State state) {
        redrawMessageList();
    }

    @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) {
    }
}