im.neon.activity.VectorRoomActivity.java Source code

Java tutorial

Introduction

Here is the source code for im.neon.activity.VectorRoomActivity.java

Source

/*
 * Copyright 2015 OpenMarket Ltd
 * Copyright 2017 Vector Creations Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package im.neon.activity;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.NotificationManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.v4.app.FragmentManager;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.text.SpannableString;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;

import org.matrix.androidsdk.crypto.MXCryptoError;
import org.matrix.androidsdk.crypto.data.MXDeviceInfo;
import org.matrix.androidsdk.crypto.data.MXUsersDevicesMap;
import org.matrix.androidsdk.util.Log;

import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

import org.matrix.androidsdk.MXSession;
import org.matrix.androidsdk.call.IMXCall;
import org.matrix.androidsdk.data.Room;
import org.matrix.androidsdk.data.RoomEmailInvitation;
import org.matrix.androidsdk.data.RoomPreviewData;
import org.matrix.androidsdk.data.RoomState;
import org.matrix.androidsdk.data.RoomSummary;
import org.matrix.androidsdk.db.MXLatestChatMessageCache;
import org.matrix.androidsdk.fragments.IconAndTextDialogFragment;
import org.matrix.androidsdk.fragments.MatrixMessageListFragment;
import org.matrix.androidsdk.listeners.IMXNetworkEventListener;
import org.matrix.androidsdk.listeners.MXEventListener;
import org.matrix.androidsdk.listeners.MXMediaUploadListener;
import org.matrix.androidsdk.rest.callback.ApiCallback;
import org.matrix.androidsdk.rest.callback.SimpleApiCallback;
import org.matrix.androidsdk.rest.model.Event;
import org.matrix.androidsdk.rest.model.MatrixError;
import org.matrix.androidsdk.rest.model.Message;
import org.matrix.androidsdk.rest.model.PowerLevels;
import org.matrix.androidsdk.rest.model.PublicRoom;
import org.matrix.androidsdk.rest.model.RoomMember;
import org.matrix.androidsdk.rest.model.User;
import org.matrix.androidsdk.util.JsonUtils;
import org.matrix.androidsdk.view.AutoScrollDownListView;

import im.neon.Matrix;
import im.neon.R;
import im.neon.VectorApp;
import im.neon.ViewedRoomTracker;
import im.neon.fragments.VectorMessageListFragment;
import im.neon.fragments.VectorUnknownDevicesFragment;
import im.neon.services.EventStreamService;
import im.neon.util.NotificationUtils;
import im.neon.util.ResourceUtils;
import im.neon.util.SharedDataItem;
import im.neon.util.SlashComandsParser;
import im.neon.util.VectorCallSoundManager;
import im.neon.util.VectorMarkdownParser;
import im.neon.util.VectorRoomMediasSender;
import im.neon.util.VectorUtils;
import im.neon.view.VectorOngoingConferenceCallView;
import im.neon.view.VectorPendingCallView;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

/**
 * Displays a single room with messages.
 */
public class VectorRoomActivity extends MXCActionBarActivity
        implements MatrixMessageListFragment.IRoomPreviewDataListener,
        MatrixMessageListFragment.IEventSendingListener, MatrixMessageListFragment.IOnScrollListener {

    /**
     * the session
     **/
    public static final String EXTRA_MATRIX_ID = MXCActionBarActivity.EXTRA_MATRIX_ID;
    /**
     * the room id (string)
     **/
    public static final String EXTRA_ROOM_ID = "EXTRA_ROOM_ID";
    /**
     * the event id (universal link management - string)
     **/
    public static final String EXTRA_EVENT_ID = "EXTRA_EVENT_ID";
    /**
     * the forwarded data (list of media uris)
     **/
    public static final String EXTRA_ROOM_INTENT = "EXTRA_ROOM_INTENT";
    /**
     * the room is opened in preview mode (string)
     **/
    public static final String EXTRA_ROOM_PREVIEW_ID = "EXTRA_ROOM_PREVIEW_ID";
    /**
     * the room alias of the room in preview mode (string)
     **/
    public static final String EXTRA_ROOM_PREVIEW_ROOM_ALIAS = "EXTRA_ROOM_PREVIEW_ROOM_ALIAS";
    /**
     * expand the room header when the activity is launched (boolean)
     **/
    public static final String EXTRA_EXPAND_ROOM_HEADER = "EXTRA_EXPAND_ROOM_HEADER";

    // display the room information while joining a room.
    // until the join is done.
    public static final String EXTRA_DEFAULT_NAME = "EXTRA_DEFAULT_NAME";
    public static final String EXTRA_DEFAULT_TOPIC = "EXTRA_DEFAULT_TOPIC";

    private static final boolean SHOW_ACTION_BAR_HEADER = true;
    private static final boolean HIDE_ACTION_BAR_HEADER = false;

    // the room is launched but it expects to start the dedicated call activity
    public static final String EXTRA_START_CALL_ID = "EXTRA_START_CALL_ID";

    private static final String TAG_FRAGMENT_MATRIX_MESSAGE_LIST = "TAG_FRAGMENT_MATRIX_MESSAGE_LIST";
    private static final String TAG_FRAGMENT_ATTACHMENTS_DIALOG = "TAG_FRAGMENT_ATTACHMENTS_DIALOG";
    private static final String TAG_FRAGMENT_CALL_OPTIONS = "TAG_FRAGMENT_CALL_OPTIONS";

    private static final String LOG_TAG = "RoomActivity";
    private static final int TYPING_TIMEOUT_MS = 10000;

    private static final String FIRST_VISIBLE_ROW = "FIRST_VISIBLE_ROW";

    // activity result request code
    private static final int REQUEST_FILES_REQUEST_CODE = 0;
    private static final int TAKE_IMAGE_REQUEST_CODE = 1;
    public static final int GET_MENTION_REQUEST_CODE = 2;
    private static final int REQUEST_ROOM_AVATAR_CODE = 3;

    private VectorMessageListFragment mVectorMessageListFragment;
    private MXSession mSession;
    private Room mRoom;
    private String mMyUserId;
    // the parameter is too big to be sent by the intent
    // so use a static variable to send it
    public static RoomPreviewData sRoomPreviewData = null;
    private String mEventId;
    private String mDefaultRoomName;
    private String mDefaultTopic;

    private MXLatestChatMessageCache mLatestChatMessageCache;

    private View mSendingMessagesLayout;
    private View mSendButtonLayout;
    private ImageView mSendImageView;
    private EditText mEditText;
    private ImageView mAvatarImageView;
    private View mCanNotPostTextView;
    private ImageView mE2eImageView;

    // call
    private View mStartCallLayout;
    private View mStopCallLayout;

    // action bar header
    private android.support.v7.widget.Toolbar mToolbar;
    private TextView mActionBarCustomTitle;
    private TextView mActionBarCustomTopic;
    private ImageView mActionBarCustomArrowImageView;
    private RelativeLayout mRoomHeaderView;
    private TextView mActionBarHeaderRoomName;
    private TextView mActionBarHeaderActiveMembers;
    private TextView mActionBarHeaderRoomTopic;
    private ImageView mActionBarHeaderRoomAvatar;
    private View mActionBarHeaderInviteMemberView;

    // notifications area
    private View mNotificationsArea;
    private ImageView mNotificationIconImageView;
    private TextView mNotificationTextView;
    private String mLatestTypingMessage;
    private Boolean mIsScrolledToTheBottom;
    private Event mLatestDisplayedEvent; // the event at the bottom of the list

    // room preview
    private View mRoomPreviewLayout;

    private MenuItem mResendUnsentMenuItem;
    private MenuItem mResendDeleteMenuItem;
    private MenuItem mSearchInRoomMenuItem;

    // medias sending helper
    private VectorRoomMediasSender mVectorRoomMediasSender;

    // pending call
    private VectorPendingCallView mVectorPendingCallView;

    // outgoing call
    private VectorOngoingConferenceCallView mVectorOngoingConferenceCallView;

    // network events
    private final IMXNetworkEventListener mNetworkEventListener = new IMXNetworkEventListener() {
        @Override
        public void onNetworkConnectionUpdate(boolean isConnected) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    refreshNotificationsArea();
                    refreshCallButtons();
                }
            });
        }
    };

    private String mCallId = null;

    private static String mLatestTakePictureCameraUri = null; // has to be String not Uri because of Serializable

    // typing event management
    private Timer mTypingTimer = null;
    private TimerTask mTypingTimerTask;
    private long mLastTypingDate = 0;

    // scroll to a dedicated index
    private int mScrollToIndex = -1;

    private boolean mIgnoreTextUpdate = false;

    // https://github.com/vector-im/vector-android/issues/323
    // on some devices, the toolbar background is set to transparent
    // when an activity is opened from this one.
    // It should not but it does.
    private boolean mIsHeaderViewDisplayed = false;

    /** **/
    private final ApiCallback<Void> mDirectMessageListener = new SimpleApiCallback<Void>(this) {
        @Override
        public void onMatrixError(MatrixError e) {
            if (MatrixError.FORBIDDEN.equals(e.errcode)) {
                Toast.makeText(VectorRoomActivity.this, e.error, Toast.LENGTH_LONG).show();
            }
        }

        @Override
        public void onSuccess(Void info) {
        }

        @Override
        public void onNetworkError(Exception e) {
            Toast.makeText(VectorRoomActivity.this, e.getMessage(), Toast.LENGTH_LONG).show();
        }

        @Override
        public void onUnexpectedError(Exception e) {
            Toast.makeText(VectorRoomActivity.this, e.getMessage(), Toast.LENGTH_LONG).show();
        }
    };

    /**
     * Presence and room preview listeners
     */
    private final MXEventListener mGlobalEventListener = new MXEventListener() {
        @Override
        public void onPresenceUpdate(Event event, User user) {
            // the header displays active members
            updateRoomHeaderMembersStatus();
        }

        @Override
        public void onLeaveRoom(String roomId) {
            // test if the user reject the invitation
            if ((null != sRoomPreviewData) && TextUtils.equals(sRoomPreviewData.getRoomId(), roomId)) {
                Log.d(LOG_TAG, "The room invitation has been declined from another client");
                onDeclined();
            }
        }

        @Override
        public void onJoinRoom(String roomId) {
            // test if the user accepts the invitation
            if ((null != sRoomPreviewData) && TextUtils.equals(sRoomPreviewData.getRoomId(), roomId)) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Log.d(LOG_TAG, "The room invitation has been accepted from another client");
                        onJoined();
                    }
                });
            }
        }
    };

    /**
     * The room events listener
     */
    private final MXEventListener mRoomEventListener = new MXEventListener() {
        @Override
        public void onRoomFlush(String roomId) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    updateActionBarTitleAndTopic();
                    updateRoomHeaderMembersStatus();
                    updateRoomHeaderAvatar();
                }
            });
        }

        @Override
        public void onLeaveRoom(String roomId) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    VectorRoomActivity.this.finish();
                }
            });
        }

        @Override
        public void onLiveEvent(final Event event, RoomState roomState) {
            VectorRoomActivity.this.runOnUiThread(new Runnable() {

                @Override
                public void run() {
                    String eventType = event.getType();

                    // The various events that could possibly change the room title
                    if (Event.EVENT_TYPE_STATE_ROOM_NAME.equals(eventType)
                            || Event.EVENT_TYPE_STATE_ROOM_ALIASES.equals(eventType)
                            || Event.EVENT_TYPE_STATE_ROOM_MEMBER.equals(eventType)) {
                        setTitle();
                        updateRoomHeaderMembersStatus();
                        updateRoomHeaderAvatar();
                    } else if (Event.EVENT_TYPE_STATE_ROOM_POWER_LEVELS.equals(eventType)) {
                        checkSendEventStatus();
                    } else if (Event.EVENT_TYPE_STATE_ROOM_TOPIC.equals(eventType)) {
                        Log.d(LOG_TAG, "Updating room topic.");
                        RoomState roomState = JsonUtils.toRoomState(event.getContent());
                        setTopic(roomState.topic);
                    } else if (Event.EVENT_TYPE_TYPING.equals(eventType)) {
                        Log.d(LOG_TAG, "on room typing");
                        onRoomTypings();
                    }
                    // header room specific
                    else if (Event.EVENT_TYPE_STATE_ROOM_AVATAR.equals(eventType)) {
                        Log.d(LOG_TAG, "Event room avatar");
                        updateRoomHeaderAvatar();
                    } else if (Event.EVENT_TYPE_MESSAGE_ENCRYPTION.equals(eventType)) {
                        boolean canSendEncryptedEvent = mRoom.isEncrypted() && mSession.isCryptoEnabled();
                        mE2eImageView.setImageResource(
                                canSendEncryptedEvent ? R.drawable.e2e_verified : R.drawable.e2e_unencrypted);
                        mVectorMessageListFragment.setIsRoomEncrypted(mRoom.isEncrypted());
                    }

                    if (!VectorApp.isAppInBackground()) {
                        // do not send read receipt for the typing events
                        // they are ephemeral ones.
                        if (!Event.EVENT_TYPE_TYPING.equals(eventType)) {
                            if (null != mRoom) {
                                refreshNotificationsArea();
                            }
                        }
                    }
                }
            });
        }

        @Override
        public void onRoomInitialSyncComplete(String roomId) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    // set general room information
                    mVectorMessageListFragment.onInitialMessagesLoaded();
                    updateActionBarTitleAndTopic();
                }
            });
        }

        @Override
        public void onBingRulesUpdate() {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    updateActionBarTitleAndTopic();
                    mVectorMessageListFragment.onBingRulesUpdate();
                }
            });
        }

        @Override
        public void onEventEncrypted(Event event) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    refreshNotificationsArea();
                }
            });
        }

        @Override
        public void onSentEvent(Event event) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    refreshNotificationsArea();
                }
            });
        }

        @Override
        public void onFailedSendingEvent(Event event) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    refreshNotificationsArea();
                }
            });
        }

        @Override
        public void onReceiptEvent(String roomId, List<String> senderIds) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    refreshNotificationsArea();
                }
            });
        }
    };

    private final IMXCall.MXCallListener mCallListener = new IMXCall.MXCallListener() {
        @Override
        public void onStateDidChange(String state) {
        }

        @Override
        public void onCallError(String error) {
            refreshCallButtons();
        }

        @Override
        public void onViewLoading(View callview) {

        }

        @Override
        public void onViewReady() {
        }

        @Override
        public void onCallAnsweredElsewhere() {
            refreshCallButtons();
        }

        @Override
        public void onCallEnd(final int aReasonId) {
            refreshCallButtons();

            // catch the flow where the hangup is done in VectorRoomActivity
            VectorCallSoundManager.releaseAudioFocus();
            // and play a lovely sound
            VectorCallSoundManager.startEndCallSound();
        }

        @Override
        public void onPreviewSizeChanged(int width, int height) {
        }
    };

    //================================================================================
    // Activity classes
    //================================================================================

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_vector_room);

        if (CommonActivityUtils.shouldRestartApp(this)) {
            Log.e(LOG_TAG, "onCreate : Restart the application.");
            CommonActivityUtils.restartApp(this);
            return;
        }

        final Intent intent = getIntent();
        if (!intent.hasExtra(EXTRA_ROOM_ID)) {
            Log.e(LOG_TAG, "No room ID extra.");
            finish();
            return;
        }

        mSession = MXCActionBarActivity.getSession(this, intent);

        if (mSession == null) {
            Log.e(LOG_TAG, "No MXSession.");
            finish();
            return;
        }

        String roomId = intent.getStringExtra(EXTRA_ROOM_ID);

        // ensure that the preview mode is really expected
        if (!intent.hasExtra(EXTRA_ROOM_PREVIEW_ID)) {
            sRoomPreviewData = null;
            Matrix.getInstance(this).clearTmpStoresList();
        }

        if (CommonActivityUtils.isGoingToSplash(this, mSession.getMyUserId(), roomId)) {
            Log.d(LOG_TAG, "onCreate : Going to splash screen");
            return;
        }

        //setDragEdge(SwipeBackLayout.DragEdge.LEFT);

        // bind the widgets of the room header view. The room header view is displayed by
        // clicking on the title of the action bar
        mRoomHeaderView = (RelativeLayout) findViewById(R.id.action_bar_header);
        mActionBarHeaderRoomTopic = (TextView) findViewById(R.id.action_bar_header_room_topic);
        mActionBarHeaderRoomName = (TextView) findViewById(R.id.action_bar_header_room_title);
        mActionBarHeaderActiveMembers = (TextView) findViewById(R.id.action_bar_header_room_members);
        mActionBarHeaderRoomAvatar = (ImageView) mRoomHeaderView.findViewById(R.id.avatar_img);
        mActionBarHeaderInviteMemberView = mRoomHeaderView.findViewById(R.id.action_bar_header_invite_members);
        mRoomPreviewLayout = findViewById(R.id.room_preview_info_layout);
        mVectorPendingCallView = (VectorPendingCallView) findViewById(R.id.room_pending_call_view);
        mVectorOngoingConferenceCallView = (VectorOngoingConferenceCallView) findViewById(
                R.id.room_ongoing_conference_call_view);
        mE2eImageView = (ImageView) findViewById(R.id.room_encrypted_image_view);

        // hide the header room as soon as the bottom layout (text edit zone) is touched
        findViewById(R.id.room_bottom_layout).setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                enableActionBarHeader(HIDE_ACTION_BAR_HEADER);
                return false;
            }
        });

        // use a toolbar instead of the actionbar
        // to be able to display an expandable header
        mToolbar = (android.support.v7.widget.Toolbar) findViewById(R.id.room_toolbar);
        this.setSupportActionBar(mToolbar);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        // set the default custom action bar layout,
        // that will be displayed from the custom action bar layout
        setActionBarDefaultCustomLayout();

        mCallId = intent.getStringExtra(EXTRA_START_CALL_ID);
        mEventId = intent.getStringExtra(EXTRA_EVENT_ID);
        mDefaultRoomName = intent.getStringExtra(EXTRA_DEFAULT_NAME);
        mDefaultTopic = intent.getStringExtra(EXTRA_DEFAULT_TOPIC);

        // the user has tapped on the "View" notification button
        if ((null != intent.getAction()) && (intent.getAction().startsWith(NotificationUtils.TAP_TO_VIEW_ACTION))) {
            // remove any pending notifications
            NotificationManager notificationsManager = (NotificationManager) this
                    .getSystemService(Context.NOTIFICATION_SERVICE);
            notificationsManager.cancelAll();
        }

        Log.d(LOG_TAG, "Displaying " + roomId);

        mEditText = (EditText) findViewById(R.id.editText_messageBox);

        // hide the header room as soon as the message input text area is touched
        mEditText.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                enableActionBarHeader(HIDE_ACTION_BAR_HEADER);
            }
        });

        // IME's DONE button is treated as a send action
        mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
            @Override
            public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
                int imeActionId = actionId & EditorInfo.IME_MASK_ACTION;

                if (EditorInfo.IME_ACTION_DONE == imeActionId) {
                    sendTextMessage();
                }

                return false;
            }
        });

        mSendingMessagesLayout = findViewById(R.id.room_sending_message_layout);
        mSendImageView = (ImageView) findViewById(R.id.room_send_image_view);
        mSendButtonLayout = findViewById(R.id.room_send_layout);
        mSendButtonLayout.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!TextUtils.isEmpty(mEditText.getText())) {
                    sendTextMessage();
                } else {
                    // hide the header room
                    enableActionBarHeader(HIDE_ACTION_BAR_HEADER);

                    FragmentManager fm = getSupportFragmentManager();
                    IconAndTextDialogFragment fragment = (IconAndTextDialogFragment) fm
                            .findFragmentByTag(TAG_FRAGMENT_ATTACHMENTS_DIALOG);

                    if (fragment != null) {
                        fragment.dismissAllowingStateLoss();
                    }

                    final Integer[] messages = new Integer[] { R.string.option_send_files,
                            R.string.option_take_photo_video, };

                    final Integer[] icons = new Integer[] { R.drawable.ic_material_file, // R.string.option_send_files
                            R.drawable.ic_material_camera, // R.string.option_take_photo
                    };

                    fragment = IconAndTextDialogFragment.newInstance(icons, messages, null,
                            ContextCompat.getColor(VectorRoomActivity.this, R.color.vector_text_black_color));
                    fragment.setOnClickListener(new IconAndTextDialogFragment.OnItemClickListener() {
                        @Override
                        public void onItemClick(IconAndTextDialogFragment dialogFragment, int position) {
                            Integer selectedVal = messages[position];

                            if (selectedVal == R.string.option_send_files) {
                                VectorRoomActivity.this.launchFileSelectionIntent();
                            } else if (selectedVal == R.string.option_take_photo_video) {
                                if (CommonActivityUtils.checkPermissions(
                                        CommonActivityUtils.REQUEST_CODE_PERMISSION_TAKE_PHOTO,
                                        VectorRoomActivity.this)) {
                                    launchCamera();
                                }
                            }
                        }
                    });

                    fragment.show(fm, TAG_FRAGMENT_ATTACHMENTS_DIALOG);
                }
            }
        });

        mEditText.addTextChangedListener(new TextWatcher() {
            @Override
            public void afterTextChanged(android.text.Editable s) {
                if (null != mRoom) {
                    MXLatestChatMessageCache latestChatMessageCache = VectorRoomActivity.this.mLatestChatMessageCache;
                    String textInPlace = latestChatMessageCache.getLatestText(VectorRoomActivity.this,
                            mRoom.getRoomId());

                    // check if there is really an update
                    // avoid useless updates (initializations..)
                    if (!mIgnoreTextUpdate && !textInPlace.equals(mEditText.getText().toString())) {
                        latestChatMessageCache.updateLatestMessage(VectorRoomActivity.this, mRoom.getRoomId(),
                                mEditText.getText().toString());
                        handleTypingNotification(mEditText.getText().length() != 0);
                    }

                    manageSendMoreButtons();
                    refreshCallButtons();
                }
            }

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

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
            }
        });

        mVectorPendingCallView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                IMXCall call = VectorCallViewActivity.getActiveCall();
                if (null != call) {
                    final Intent intent = new Intent(VectorRoomActivity.this, VectorCallViewActivity.class);
                    intent.putExtra(VectorCallViewActivity.EXTRA_MATRIX_ID,
                            call.getSession().getCredentials().userId);
                    intent.putExtra(VectorCallViewActivity.EXTRA_CALL_ID, call.getCallId());

                    VectorRoomActivity.this.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            VectorRoomActivity.this.startActivity(intent);
                        }
                    });
                } else {
                    // if the call is no more active, just remove the view
                    mVectorPendingCallView.onCallTerminated();
                }
            }
        });

        mActionBarHeaderInviteMemberView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                launchRoomDetails(VectorRoomDetailsActivity.PEOPLE_TAB_INDEX);
            }
        });

        // notifications area
        mNotificationsArea = findViewById(R.id.room_notifications_area);
        mNotificationIconImageView = (ImageView) mNotificationsArea.findViewById(R.id.room_notification_icon);
        mNotificationTextView = (TextView) mNotificationsArea.findViewById(R.id.room_notification_message);

        mCanNotPostTextView = findViewById(R.id.room_cannot_post_textview);

        // increase the clickable area to open the keyboard.
        // when there is no text, it is quite small and some user thought the edition was disabled.
        findViewById(R.id.room_sending_message_layout).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mEditText.requestFocus()) {
                    InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                    imm.showSoftInput(mEditText, InputMethodManager.SHOW_IMPLICIT);
                }
            }
        });

        mStartCallLayout = findViewById(R.id.room_start_call_layout);
        mStartCallLayout.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if ((null != mRoom) && mRoom.isEncrypted() && (mRoom.getActiveMembers().size() > 2)) {
                    // display the dialog with the info text
                    AlertDialog.Builder permissionsInfoDialog = new AlertDialog.Builder(VectorRoomActivity.this);
                    Resources resource = getResources();
                    permissionsInfoDialog
                            .setMessage(resource.getString(R.string.room_no_conference_call_in_encrypted_rooms));
                    permissionsInfoDialog.setIcon(android.R.drawable.ic_dialog_alert);
                    permissionsInfoDialog.setPositiveButton(resource.getString(R.string.ok), null);
                    permissionsInfoDialog.show();

                } else if (isUserAllowedToStartConfCall()) {
                    displayVideoCallIpDialog();
                } else {
                    displayConfCallNotAllowed();
                }
            }
        });

        mStopCallLayout = findViewById(R.id.room_end_call_layout);
        mStopCallLayout.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                IMXCall call = mSession.mCallsManager.getCallWithRoomId(mRoom.getRoomId());

                if (null != call) {
                    call.hangup(null);
                }
            }
        });

        findViewById(R.id.room_button_margin_right).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // extend the right side of right button
                // to avoid clicking in the void
                if (mStopCallLayout.getVisibility() == View.VISIBLE) {
                    mStopCallLayout.performClick();
                } else if (mStartCallLayout.getVisibility() == View.VISIBLE) {
                    mStartCallLayout.performClick();
                } else if (mSendButtonLayout.getVisibility() == View.VISIBLE) {
                    mSendButtonLayout.performClick();
                }
            }
        });

        mMyUserId = mSession.getCredentials().userId;

        CommonActivityUtils.resumeEventStream(this);

        mRoom = mSession.getDataHandler().getRoom(roomId, false);

        FragmentManager fm = getSupportFragmentManager();
        mVectorMessageListFragment = (VectorMessageListFragment) fm
                .findFragmentByTag(TAG_FRAGMENT_MATRIX_MESSAGE_LIST);

        if (mVectorMessageListFragment == null) {
            Log.d(LOG_TAG, "Create VectorMessageListFragment");

            // this fragment displays messages and handles all message logic
            mVectorMessageListFragment = VectorMessageListFragment.newInstance(mMyUserId, roomId, mEventId,
                    (null == sRoomPreviewData) ? null : VectorMessageListFragment.PREVIEW_MODE_READ_ONLY,
                    org.matrix.androidsdk.R.layout.fragment_matrix_message_list_fragment);
            fm.beginTransaction().add(R.id.anchor_fragment_messages, mVectorMessageListFragment,
                    TAG_FRAGMENT_MATRIX_MESSAGE_LIST).commit();
        } else {
            Log.d(LOG_TAG, "Reuse VectorMessageListFragment");
        }

        mVectorRoomMediasSender = new VectorRoomMediasSender(this, mVectorMessageListFragment,
                Matrix.getInstance(this).getMediasCache());
        mVectorRoomMediasSender.onRestoreInstanceState(savedInstanceState);

        manageRoomPreview();

        addRoomHeaderClickListeners();

        // in timeline mode (i.e search in the forward and backward room history)
        // or in room preview mode
        // the edition items are not displayed
        if (!TextUtils.isEmpty(mEventId) || (null != sRoomPreviewData)) {
            mNotificationsArea.setVisibility(View.GONE);
            findViewById(R.id.bottom_separator).setVisibility(View.GONE);
            findViewById(R.id.room_notification_separator).setVisibility(View.GONE);
            findViewById(R.id.room_notifications_area).setVisibility(View.GONE);

            View v = findViewById(R.id.room_bottom_layout);
            ViewGroup.LayoutParams params = v.getLayoutParams();
            params.height = 0;
            v.setLayoutParams(params);
        }

        mLatestChatMessageCache = Matrix.getInstance(this).getDefaultLatestChatMessageCache();

        // some medias must be sent while opening the chat
        if (intent.hasExtra(EXTRA_ROOM_INTENT)) {
            final Intent mediaIntent = intent.getParcelableExtra(EXTRA_ROOM_INTENT);

            // sanity check
            if (null != mediaIntent) {
                mEditText.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        intent.removeExtra(EXTRA_ROOM_INTENT);
                        sendMediasIntent(mediaIntent);
                    }
                }, 1000);
            }
        }

        mVectorOngoingConferenceCallView.initRoomInfo(mSession, mRoom);
        mVectorOngoingConferenceCallView
                .setCallClickListener(new VectorOngoingConferenceCallView.ICallClickListener() {

                    private void startCall(boolean isVideo) {
                        if (CommonActivityUtils.checkPermissions(
                                isVideo ? CommonActivityUtils.REQUEST_CODE_PERMISSION_VIDEO_IP_CALL
                                        : CommonActivityUtils.REQUEST_CODE_PERMISSION_AUDIO_IP_CALL,
                                VectorRoomActivity.this)) {
                            startIpCall(isVideo);
                        }
                    }

                    @Override
                    public void onVoiceCallClick() {
                        startCall(false);
                    }

                    @Override
                    public void onVideoCallClick() {
                        startCall(true);
                    }
                });

        View avatarLayout = findViewById(R.id.room_self_avatar);

        if (null != avatarLayout) {
            mAvatarImageView = (ImageView) avatarLayout.findViewById(R.id.avatar_img);
        }

        refreshSelfAvatar();

        // in case a "Send as" dialog was in progress when the activity was destroyed (life cycle)
        mVectorRoomMediasSender.resumeResizeMediaAndSend();

        // header visibility has launched
        enableActionBarHeader(intent.getBooleanExtra(EXTRA_EXPAND_ROOM_HEADER, false) ? SHOW_ACTION_BAR_HEADER
                : HIDE_ACTION_BAR_HEADER);

        // the both flags are only used once
        intent.removeExtra(EXTRA_EXPAND_ROOM_HEADER);

        Log.d(LOG_TAG, "End of create");
    }

    @Override
    public void onSaveInstanceState(Bundle savedInstanceState) {
        // Always call the superclass so it can save the view hierarchy state
        super.onSaveInstanceState(savedInstanceState);

        mVectorRoomMediasSender.onSaveInstanceState(savedInstanceState);
        savedInstanceState.putInt(FIRST_VISIBLE_ROW,
                mVectorMessageListFragment.mMessageListView.getFirstVisiblePosition());
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);

        // the listView will be refreshed so the offset might be lost.
        mScrollToIndex = savedInstanceState.getInt(FIRST_VISIBLE_ROW, -1);
    }

    @Override
    public void onDestroy() {
        if (null != mVectorMessageListFragment) {
            mVectorMessageListFragment.onDestroy();
        }

        if (null != mVectorOngoingConferenceCallView) {
            mVectorOngoingConferenceCallView.setCallClickListener(null);
        }

        super.onDestroy();
    }

    @Override
    protected void onPause() {
        super.onPause();
        // warn other member that the typing is ended
        cancelTypingNotification();

        if (null != mRoom) {
            // listen for room name or topic changes
            mRoom.removeEventListener(mRoomEventListener);
        }

        Matrix.getInstance(this).removeNetworkEventListener(mNetworkEventListener);

        if (mSession.isAlive()) {
            // GA reports a null dataHandler instance event if it seems impossible
            if (null != mSession.getDataHandler()) {
                mSession.getDataHandler().removeListener(mGlobalEventListener);
            }
        }

        mVectorOngoingConferenceCallView.onActivityPause();

        // to have notifications for this room
        ViewedRoomTracker.getInstance().setViewedRoomId(null);
        ViewedRoomTracker.getInstance().setMatrixId(null);
    }

    @Override
    protected void onResume() {
        Log.d(LOG_TAG, "++ Resume the activity");
        super.onResume();

        ViewedRoomTracker.getInstance().setMatrixId(mSession.getCredentials().userId);

        if (null != mRoom) {
            // to do not trigger notifications for this room
            // because it is displayed.
            ViewedRoomTracker.getInstance().setViewedRoomId(mRoom.getRoomId());

            // check if the room has been left from another client.
            if (mRoom.isReady()) {
                if ((null == mRoom.getMember(mMyUserId))
                        || !mSession.getDataHandler().doesRoomExist(mRoom.getRoomId())) {
                    VectorRoomActivity.this.finish();
                    return;
                }
            }

            // listen for room name or topic changes
            mRoom.addEventListener(mRoomEventListener);

            mEditText.setHint(mRoom.isEncrypted() ? R.string.room_message_placeholder_encrypted
                    : R.string.room_message_placeholder_not_encrypted);
        }

        mSession.getDataHandler().addListener(mGlobalEventListener);

        Matrix.getInstance(this).addNetworkEventListener(mNetworkEventListener);

        if (null != mRoom) {
            EventStreamService.cancelNotificationsForRoomId(mSession.getCredentials().userId, mRoom.getRoomId());
        }

        // sanity checks
        if ((null != mRoom) && (null != Matrix.getInstance(this).getDefaultLatestChatMessageCache())) {
            String cachedText = Matrix.getInstance(this).getDefaultLatestChatMessageCache().getLatestText(this,
                    mRoom.getRoomId());

            if (!cachedText.equals(mEditText.getText().toString())) {
                mIgnoreTextUpdate = true;
                mEditText.setText("");
                mEditText.append(cachedText);
                mIgnoreTextUpdate = false;
            }

            mVectorMessageListFragment.setIsRoomEncrypted(mRoom.isEncrypted());

            boolean canSendEncryptedEvent = mRoom.isEncrypted() && mSession.isCryptoEnabled();
            mE2eImageView
                    .setImageResource(canSendEncryptedEvent ? R.drawable.e2e_verified : R.drawable.e2e_unencrypted);
            mVectorMessageListFragment.setIsRoomEncrypted(mRoom.isEncrypted());
        }

        manageSendMoreButtons();

        updateActionBarTitleAndTopic();

        sendReadReceipt();

        refreshCallButtons();

        updateRoomHeaderMembersStatus();

        checkSendEventStatus();

        enableActionBarHeader(mIsHeaderViewDisplayed);

        // refresh the UI : the timezone could have been updated
        mVectorMessageListFragment.refresh();

        // the list automatically scrolls down when its top moves down
        if (mVectorMessageListFragment.mMessageListView instanceof AutoScrollDownListView) {
            ((AutoScrollDownListView) mVectorMessageListFragment.mMessageListView).lockSelectionOnResize();
        }

        // the device has been rotated
        // so try to keep the same top/left item;
        if (mScrollToIndex > 0) {
            mVectorMessageListFragment.scrollToIndexWhenLoaded(mScrollToIndex);
            mScrollToIndex = -1;
        }

        if (null != mCallId) {
            IMXCall call = VectorCallViewActivity.getActiveCall();

            // can only manage one call instance.
            // either there is no active call or resume the active one
            if ((null == call) || call.getCallId().equals(mCallId)) {
                final Intent intent = new Intent(VectorRoomActivity.this, VectorCallViewActivity.class);
                intent.putExtra(VectorCallViewActivity.EXTRA_MATRIX_ID, mSession.getCredentials().userId);
                intent.putExtra(VectorCallViewActivity.EXTRA_CALL_ID, mCallId);

                if (null == call) {
                    intent.putExtra(VectorCallViewActivity.EXTRA_AUTO_ACCEPT, "anything");
                }

                enableActionBarHeader(HIDE_ACTION_BAR_HEADER);
                VectorRoomActivity.this.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        VectorRoomActivity.this.startActivity(intent);
                    }
                });

            }

            mCallId = null;
        }

        if (null != mRoom) {
            // check if the room has been left from another activity
            if (mRoom.isLeaving() || !mSession.getDataHandler().doesRoomExist(mRoom.getRoomId())) {

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        VectorRoomActivity.this.finish();
                    }
                });
            }
        }

        // the pending call view is only displayed with "active " room
        if ((null == sRoomPreviewData) && (null == mEventId)) {
            mVectorPendingCallView.checkPendingCall();
            mVectorOngoingConferenceCallView.onActivityResume();
        }

        displayE2eRoomAlert();

        Log.d(LOG_TAG, "-- Resume the activity");
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (resultCode == RESULT_OK) {
            if ((requestCode == REQUEST_FILES_REQUEST_CODE) || (requestCode == TAKE_IMAGE_REQUEST_CODE)) {
                sendMediasIntent(data);
            } else if (requestCode == GET_MENTION_REQUEST_CODE) {
                insertUserDisplayNameInTextEditor(
                        data.getStringExtra(VectorMemberDetailsActivity.RESULT_MENTION_ID));
            } else if (requestCode == REQUEST_ROOM_AVATAR_CODE) {
                onActivityResultRoomAvatarUpdate(data);
            }
        }
    }

    //================================================================================
    // IEventSendingListener
    //================================================================================

    @Override
    public void onMessageSendingSucceeded(Event event) {
        refreshNotificationsArea();
    }

    @Override
    public void onMessageSendingFailed(Event event) {
        refreshNotificationsArea();
    }

    @Override
    public void onMessageRedacted(Event event) {
        refreshNotificationsArea();
    }

    @Override
    public void onUnknownDevices(Event event, MXCryptoError error) {
        refreshNotificationsArea();
        CommonActivityUtils.displayUnknownDevicesDialog(mSession, this,
                (MXUsersDevicesMap<MXDeviceInfo>) error.mExceptionData,
                new VectorUnknownDevicesFragment.IUnknownDevicesSendAnywayListener() {
                    @Override
                    public void onSendAnyway() {
                        mVectorMessageListFragment.resendUnsentMessages();
                        refreshNotificationsArea();
                    }
                });
    }

    //================================================================================
    // IOnScrollListener
    //================================================================================

    /**
     * Send a read receipt to the latest displayed event.
     */
    private void sendReadReceipt() {
        if ((null != mRoom) && (null == sRoomPreviewData)) {
            // send the read receipt
            mRoom.sendReadReceipt(mLatestDisplayedEvent, null);
            refreshNotificationsArea();
        }
    }

    @Override
    public void onScroll(int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        Event eventAtBottom = mVectorMessageListFragment.getEvent(firstVisibleItem + visibleItemCount - 1);

        if ((null != eventAtBottom) && ((null == mLatestDisplayedEvent)
                || !TextUtils.equals(eventAtBottom.eventId, mLatestDisplayedEvent.eventId))) {

            Log.d(LOG_TAG, "## onScroll firstVisibleItem " + firstVisibleItem + " visibleItemCount "
                    + visibleItemCount + " totalItemCount " + totalItemCount);
            mLatestDisplayedEvent = eventAtBottom;

            // don't send receive if the app is in background
            if (!VectorApp.isAppInBackground()) {
                sendReadReceipt();
            } else {
                Log.d(LOG_TAG, "## onScroll : the app is in background");
            }
        }
    }

    @Override
    public void onLatestEventDisplay(boolean isDisplayed) {
        // not yet initialized or a new value
        if ((null == mIsScrolledToTheBottom) || (isDisplayed != mIsScrolledToTheBottom)) {
            Log.d(LOG_TAG, "## onLatestEventDisplay : isDisplayed " + isDisplayed);

            if (isDisplayed && (null != mRoom)) {
                mLatestDisplayedEvent = mRoom.getDataHandler().getStore().getLatestEvent(mRoom.getRoomId());
                // ensure that the latest message is displayed
                mRoom.sendReadReceipt(null);
            }

            mIsScrolledToTheBottom = isDisplayed;
            refreshNotificationsArea();
        }
    }

    //================================================================================
    // Menu management
    //================================================================================

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // the application is in a weird state
        // GA : mSession is null
        if (CommonActivityUtils.shouldRestartApp(this) || (null == mSession)) {
            return false;
        }

        // the menu is only displayed when the current activity does not display a timeline search
        if (TextUtils.isEmpty(mEventId) && (null == sRoomPreviewData)) {
            // Inflate the menu; this adds items to the action bar if it is present.
            getMenuInflater().inflate(R.menu.vector_room, menu);

            mResendUnsentMenuItem = menu.findItem(R.id.ic_action_room_resend_unsent);
            mResendDeleteMenuItem = menu.findItem(R.id.ic_action_room_delete_unsent);
            mSearchInRoomMenuItem = menu.findItem(R.id.ic_action_search_in_room);

            // hide / show the unsent / resend all entries.
            refreshNotificationsArea();
        }

        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();

        if (id == R.id.ic_action_search_in_room) {
            try {
                enableActionBarHeader(HIDE_ACTION_BAR_HEADER);

                final Intent searchIntent = new Intent(VectorRoomActivity.this, VectorUnifiedSearchActivity.class);
                searchIntent.putExtra(VectorUnifiedSearchActivity.EXTRA_ROOM_ID, mRoom.getRoomId());
                VectorRoomActivity.this.startActivity(searchIntent);

            } catch (Exception e) {
                Log.i(LOG_TAG, "## onOptionsItemSelected(): ");
            }
        } else if (id == R.id.ic_action_room_settings) {
            launchRoomDetails(VectorRoomDetailsActivity.PEOPLE_TAB_INDEX);
        } else if (id == R.id.ic_action_room_resend_unsent) {
            mVectorMessageListFragment.resendUnsentMessages();
            refreshNotificationsArea();
        } else if (id == R.id.ic_action_room_delete_unsent) {
            mVectorMessageListFragment.deleteUnsentMessages();
            refreshNotificationsArea();
        } else if (id == R.id.ic_action_room_leave) {
            if (null != mRoom) {
                Log.d(LOG_TAG, "Leave the room " + mRoom.getRoomId());
                new AlertDialog.Builder(VectorApp.getCurrentActivity())
                        .setTitle(R.string.room_participants_leave_prompt_title)
                        .setMessage(R.string.room_participants_leave_prompt_msg)
                        .setPositiveButton(R.string.leave, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.dismiss();
                                setProgressVisibility(View.VISIBLE);

                                mRoom.leave(new ApiCallback<Void>() {
                                    @Override
                                    public void onSuccess(Void info) {
                                        Log.d(LOG_TAG, "The room " + mRoom.getRoomId() + " is left");
                                        // close the activity
                                        finish();
                                    }

                                    private void onError(String errorMessage) {
                                        setProgressVisibility(View.GONE);
                                        Log.e(LOG_TAG, "Cannot leave the room " + mRoom.getRoomId() + " : "
                                                + errorMessage);
                                    }

                                    @Override
                                    public void onNetworkError(Exception e) {
                                        onError(e.getLocalizedMessage());
                                    }

                                    @Override
                                    public void onMatrixError(MatrixError e) {
                                        onError(e.getLocalizedMessage());
                                    }

                                    @Override
                                    public void onUnexpectedError(Exception e) {
                                        onError(e.getLocalizedMessage());
                                    }
                                });
                            }
                        }).setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.dismiss();
                            }
                        }).create().show();
            }
        }

        return super.onOptionsItemSelected(item);
    }

    /**
     * Check if the current user is allowed to perform a conf call.
     * The user power level is checked against the invite power level.
     * <p>To start a conf call, the user needs to invite the CFU to the room.
     *
     * @return true if the user is allowed, false otherwise
     */
    private boolean isUserAllowedToStartConfCall() {
        boolean isAllowed = false;

        if (mRoom.isOngoingConferenceCall()) {
            // if a conf is in progress, the user can join the established conf anyway
            Log.d(LOG_TAG, "## isUserAllowedToStartConfCall(): conference in progress");
            isAllowed = true;
        } else if ((null != mRoom) && (mRoom.getActiveMembers().size() > 2)) {
            PowerLevels powerLevels = mRoom.getLiveState().getPowerLevels();

            if (null != powerLevels) {
                // to start a conf call, the user MUST have the power to invite someone (CFU)
                isAllowed = powerLevels.getUserPowerLevel(mSession.getMyUserId()) >= powerLevels.invite;
            }
        } else {
            // 1:1 call
            isAllowed = true;
        }

        Log.d(LOG_TAG, "## isUserAllowedToStartConfCall(): isAllowed=" + isAllowed);
        return isAllowed;
    }

    /**
     * Display a dialog box to indicate that the conf call can no be performed.
     * <p>See {@link #isUserAllowedToStartConfCall()}
     */
    private void displayConfCallNotAllowed() {
        // display the dialog with the info text
        AlertDialog.Builder permissionsInfoDialog = new AlertDialog.Builder(VectorRoomActivity.this);
        Resources resource = getResources();

        if ((null != resource) && (null != permissionsInfoDialog)) {
            permissionsInfoDialog
                    .setTitle(resource.getString(R.string.missing_permissions_title_to_start_conf_call));
            permissionsInfoDialog.setMessage(resource.getString(R.string.missing_permissions_to_start_conf_call));

            permissionsInfoDialog.setIcon(android.R.drawable.ic_dialog_alert);
            permissionsInfoDialog.setPositiveButton(resource.getString(R.string.ok), null);
            permissionsInfoDialog.show();
        } else {
            Log.e(LOG_TAG, "## displayConfCallNotAllowed(): impossible to create dialog");
        }
    }

    /**
     * Start an IP call with the management of the corresponding permissions.
     * According to the IP call, the corresponding permissions are asked: {@link CommonActivityUtils#REQUEST_CODE_PERMISSION_AUDIO_IP_CALL}
     * or {@link CommonActivityUtils#REQUEST_CODE_PERMISSION_VIDEO_IP_CALL}.
     */
    private void displayVideoCallIpDialog() {
        // hide the header room
        enableActionBarHeader(HIDE_ACTION_BAR_HEADER);

        final Integer[] lIcons = new Integer[] { R.drawable.voice_call_black, R.drawable.video_call_black };
        final Integer[] lTexts = new Integer[] { R.string.action_voice_call, R.string.action_video_call };

        IconAndTextDialogFragment fragment = IconAndTextDialogFragment.newInstance(lIcons, lTexts);
        fragment.setOnClickListener(new IconAndTextDialogFragment.OnItemClickListener() {
            @Override
            public void onItemClick(IconAndTextDialogFragment dialogFragment, int position) {
                boolean isVideoCall = false;
                int requestCode = CommonActivityUtils.REQUEST_CODE_PERMISSION_AUDIO_IP_CALL;

                if (1 == position) {
                    isVideoCall = true;
                    requestCode = CommonActivityUtils.REQUEST_CODE_PERMISSION_VIDEO_IP_CALL;
                }

                if (CommonActivityUtils.checkPermissions(requestCode, VectorRoomActivity.this)) {
                    startIpCall(isVideoCall);
                }
            }
        });

        // display the fragment dialog
        fragment.show(getSupportFragmentManager(), TAG_FRAGMENT_CALL_OPTIONS);
    }

    /**
     * Start an IP call: audio call if aIsVideoCall is false or video call if aIsVideoCall
     * is true.
     *
     * @param aIsVideoCall true to video call, false to audio call
     */
    private void startIpCall(final boolean aIsVideoCall) {
        enableActionBarHeader(HIDE_ACTION_BAR_HEADER);

        setProgressVisibility(View.VISIBLE);

        // create the call object
        mSession.mCallsManager.createCallInRoom(mRoom.getRoomId(), new ApiCallback<IMXCall>() {
            @Override
            public void onSuccess(final IMXCall call) {
                Log.d(LOG_TAG, "## startIpCall(): onSuccess");
                VectorRoomActivity.this.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        setProgressVisibility(View.GONE);
                        call.setIsVideo(aIsVideoCall);
                        call.setIsIncoming(false);

                        final Intent intent = new Intent(VectorRoomActivity.this, VectorCallViewActivity.class);

                        intent.putExtra(VectorCallViewActivity.EXTRA_MATRIX_ID, mSession.getCredentials().userId);
                        intent.putExtra(VectorCallViewActivity.EXTRA_CALL_ID, call.getCallId());

                        VectorRoomActivity.this.runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                VectorRoomActivity.this.startActivity(intent);
                            }
                        });
                    }
                });
            }

            private void onError(final String errorMessage) {
                VectorRoomActivity.this.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        setProgressVisibility(View.GONE);
                        Activity activity = VectorRoomActivity.this;
                        CommonActivityUtils.displayToastOnUiThread(activity,
                                activity.getString(R.string.cannot_start_call) + " (" + errorMessage + ")");
                    }
                });
            }

            @Override
            public void onNetworkError(Exception e) {
                Log.e(LOG_TAG, "## startIpCall(): onNetworkError Msg=" + e.getMessage());
                onError(e.getLocalizedMessage());
            }

            @Override
            public void onMatrixError(MatrixError e) {
                Log.e(LOG_TAG, "## startIpCall(): onMatrixError Msg=" + e.getLocalizedMessage());

                if (e instanceof MXCryptoError) {
                    MXCryptoError cryptoError = (MXCryptoError) e;
                    if (MXCryptoError.UNKNOWN_DEVICES_CODE.equals(cryptoError.errcode)) {
                        setProgressVisibility(View.GONE);
                        CommonActivityUtils.displayUnknownDevicesDialog(mSession, VectorRoomActivity.this,
                                (MXUsersDevicesMap<MXDeviceInfo>) cryptoError.mExceptionData,
                                new VectorUnknownDevicesFragment.IUnknownDevicesSendAnywayListener() {
                                    @Override
                                    public void onSendAnyway() {
                                        startIpCall(aIsVideoCall);
                                    }
                                });

                        return;
                    }
                }

                onError(e.getLocalizedMessage());
            }

            @Override
            public void onUnexpectedError(Exception e) {
                Log.e(LOG_TAG, "## startIpCall(): onUnexpectedError Msg=" + e.getLocalizedMessage());
                onError(e.getLocalizedMessage());
            }
        });
    }

    //================================================================================
    // messages sending
    //================================================================================

    /**
     * Cancels the room selection mode.
     */
    public void cancelSelectionMode() {
        mVectorMessageListFragment.cancelSelectionMode();
    }

    /**
     * Send the editText text.
     */
    private void sendTextMessage() {
        VectorApp.markdownToHtml(mEditText.getText().toString().trim(),
                new VectorMarkdownParser.IVectorMarkdownParserListener() {
                    @Override
                    public void onMarkdownParsed(final String text, final String HTMLText) {
                        VectorRoomActivity.this.runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                enableActionBarHeader(HIDE_ACTION_BAR_HEADER);
                                sendMessage(text, TextUtils.equals(text, HTMLText) ? null : HTMLText,
                                        Message.FORMAT_MATRIX_HTML);
                                mEditText.setText("");
                            }
                        });
                    }
                });
    }

    /**
     * Send a text message with its formatted format
     *
     * @param body          the text message.
     * @param formattedBody the formatted message
     * @param format        the message format
     */
    public void sendMessage(String body, String formattedBody, String format) {
        if (!TextUtils.isEmpty(body)) {
            if (!SlashComandsParser.manageSplashCommand(this, mSession, mRoom, body, formattedBody, format)) {
                cancelSelectionMode();
                mVectorMessageListFragment.sendTextMessage(body, formattedBody, format);
            }
        }
    }

    /**
     * Send an emote in the opened room
     *
     * @param emote the emote
     */
    public void sendEmote(String emote, String formattedEmote, String format) {
        if (null != mVectorMessageListFragment) {
            mVectorMessageListFragment.sendEmote(emote, formattedEmote, format);
        }
    }

    @SuppressLint("NewApi")
    /**
     * Send the medias defined in the intent.
     * They are listed, checked and sent when it is possible.
     */
    private void sendMediasIntent(final Intent intent) {
        // sanity check
        if ((null == intent) && (null == mLatestTakePictureCameraUri)) {
            return;
        }

        ArrayList<SharedDataItem> sharedDataItems = new ArrayList<>();

        if (null != intent) {
            sharedDataItems = new ArrayList<>(SharedDataItem.listSharedDataItems(intent));
        } else if (null != mLatestTakePictureCameraUri) {
            sharedDataItems.add(new SharedDataItem(Uri.parse(mLatestTakePictureCameraUri)));
            mLatestTakePictureCameraUri = null;
        }

        // check the extras
        if (0 == sharedDataItems.size()) {
            Bundle bundle = intent.getExtras();

            // sanity checks
            if (null != bundle) {
                bundle.setClassLoader(SharedDataItem.class.getClassLoader());

                if (bundle.containsKey(Intent.EXTRA_STREAM)) {
                    try {
                        Object streamUri = bundle.get(Intent.EXTRA_STREAM);

                        if (streamUri instanceof Uri) {
                            sharedDataItems.add(new SharedDataItem((Uri) streamUri));
                        } else if (streamUri instanceof List) {
                            List<Object> streams = (List<Object>) streamUri;

                            for (Object object : streams) {
                                if (object instanceof Uri) {
                                    sharedDataItems.add(new SharedDataItem((Uri) object));
                                } else if (object instanceof SharedDataItem) {
                                    sharedDataItems.add((SharedDataItem) object);
                                }
                            }
                        }
                    } catch (Exception e) {
                        Log.e(LOG_TAG, "fail to extract the extra stream");
                    }
                } else if (bundle.containsKey(Intent.EXTRA_TEXT)) {
                    mEditText.setText(mEditText.getText() + bundle.getString(Intent.EXTRA_TEXT));

                    mEditText.post(new Runnable() {
                        @Override
                        public void run() {
                            mEditText.setSelection(mEditText.getText().length());
                        }
                    });
                }
            }
        }

        if (0 != sharedDataItems.size()) {
            mVectorRoomMediasSender.sendMedias(sharedDataItems);
        }
    }

    //================================================================================
    // typing
    //================================================================================

    /**
     * send a typing event notification
     *
     * @param isTyping typing param
     */
    private void handleTypingNotification(boolean isTyping) {
        int notificationTimeoutMS = -1;
        if (isTyping) {
            // Check whether a typing event has been already reported to server (We wait for the end of the local timeout before considering this new event)
            if (null != mTypingTimer) {
                // Refresh date of the last observed typing
                System.currentTimeMillis();
                mLastTypingDate = System.currentTimeMillis();
                return;
            }

            int timerTimeoutInMs = TYPING_TIMEOUT_MS;

            if (0 != mLastTypingDate) {
                long lastTypingAge = System.currentTimeMillis() - mLastTypingDate;
                if (lastTypingAge < timerTimeoutInMs) {
                    // Subtract the time interval since last typing from the timer timeout
                    timerTimeoutInMs -= lastTypingAge;
                } else {
                    timerTimeoutInMs = 0;
                }
            } else {
                // Keep date of this typing event
                mLastTypingDate = System.currentTimeMillis();
            }

            if (timerTimeoutInMs > 0) {

                mTypingTimerTask = new TimerTask() {
                    public void run() {
                        synchronized (LOG_TAG) {
                            if (mTypingTimerTask != null) {
                                mTypingTimerTask.cancel();
                                mTypingTimerTask = null;
                            }

                            if (mTypingTimer != null) {
                                mTypingTimer.cancel();
                                mTypingTimer = null;
                            }
                            // Post a new typing notification
                            VectorRoomActivity.this.handleTypingNotification(0 != mLastTypingDate);
                        }
                    }
                };

                try {
                    synchronized (LOG_TAG) {
                        mTypingTimer = new Timer();
                        mTypingTimer.schedule(mTypingTimerTask, TYPING_TIMEOUT_MS);
                    }
                } catch (Exception e) {
                    Log.e(LOG_TAG, "fails to launch typing timer " + e.getLocalizedMessage());
                }

                // Compute the notification timeout in ms (consider the double of the local typing timeout)
                notificationTimeoutMS = TYPING_TIMEOUT_MS * 2;
            } else {
                // This typing event is too old, we will ignore it
                isTyping = false;
            }
        } else {
            // Cancel any typing timer
            if (mTypingTimerTask != null) {
                mTypingTimerTask.cancel();
                mTypingTimerTask = null;
            }

            if (mTypingTimer != null) {
                mTypingTimer.cancel();
                mTypingTimer = null;
            }
            // Reset last typing date
            mLastTypingDate = 0;
        }

        final boolean typingStatus = isTyping;

        mRoom.sendTypingNotification(typingStatus, notificationTimeoutMS,
                new SimpleApiCallback<Void>(VectorRoomActivity.this) {
                    @Override
                    public void onSuccess(Void info) {
                        // Reset last typing date
                        mLastTypingDate = 0;
                    }

                    @Override
                    public void onNetworkError(Exception e) {
                        if (mTypingTimerTask != null) {
                            mTypingTimerTask.cancel();
                            mTypingTimerTask = null;
                        }

                        if (mTypingTimer != null) {
                            mTypingTimer.cancel();
                            mTypingTimer = null;
                        }
                        // do not send again
                        // assume that the typing event is optional
                    }
                });
    }

    private void cancelTypingNotification() {
        if (0 != mLastTypingDate) {
            if (mTypingTimerTask != null) {
                mTypingTimerTask.cancel();
                mTypingTimerTask = null;
            }
            if (mTypingTimer != null) {
                mTypingTimer.cancel();
                mTypingTimer = null;
            }

            mLastTypingDate = 0;

            mRoom.sendTypingNotification(false, -1, new SimpleApiCallback<Void>(VectorRoomActivity.this) {
            });
        }
    }

    //================================================================================
    // Actions
    //================================================================================

    /**
     * Update the spinner visibility.
     *
     * @param visibility the visibility.
     */
    public void setProgressVisibility(int visibility) {
        View progressLayout = findViewById(R.id.main_progress_layout);

        if ((null != progressLayout) && (progressLayout.getVisibility() != visibility)) {
            progressLayout.setVisibility(visibility);
        }
    }

    /**
     * Launch the room details activity with a selected tab.
     *
     * @param selectedTab the selected tab index.
     */
    private void launchRoomDetails(int selectedTab) {
        if ((null != mRoom) && (null != mRoom.getMember(mSession.getMyUserId()))) {
            enableActionBarHeader(HIDE_ACTION_BAR_HEADER);

            // pop to the home activity
            Intent intent = new Intent(VectorRoomActivity.this, VectorRoomDetailsActivity.class);
            intent.putExtra(VectorRoomDetailsActivity.EXTRA_ROOM_ID, mRoom.getRoomId());
            intent.putExtra(VectorRoomDetailsActivity.EXTRA_MATRIX_ID, mSession.getCredentials().userId);
            intent.putExtra(VectorRoomDetailsActivity.EXTRA_SELECTED_TAB_ID, selectedTab);
            startActivityForResult(intent, GET_MENTION_REQUEST_CODE);
        }
    }

    /**
     * Launch the files selection intent
     */
    @SuppressLint("NewApi")
    private void launchFileSelectionIntent() {
        enableActionBarHeader(HIDE_ACTION_BAR_HEADER);

        Intent fileIntent = new Intent(Intent.ACTION_GET_CONTENT);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            fileIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
        }
        fileIntent.setType("*/*");
        startActivityForResult(fileIntent, REQUEST_FILES_REQUEST_CODE);
    }

    /**
     * Launch the camera
     */
    private void launchCamera() {
        enableActionBarHeader(HIDE_ACTION_BAR_HEADER);

        Intent intent = new Intent(this, VectorMediasPickerActivity.class);
        intent.putExtra(VectorMediasPickerActivity.EXTRA_VIDEO_RECORDING_MODE, true);
        startActivityForResult(intent, TAKE_IMAGE_REQUEST_CODE);
    }

    @Override
    public void onRequestPermissionsResult(int aRequestCode, @NonNull String[] aPermissions,
            @NonNull int[] aGrantResults) {
        if (0 == aPermissions.length) {
            Log.e(LOG_TAG, "## onRequestPermissionsResult(): cancelled " + aRequestCode);
        } else if (aRequestCode == CommonActivityUtils.REQUEST_CODE_PERMISSION_ROOM_DETAILS) {
            boolean isCameraPermissionGranted = false;

            for (int i = 0; i < aPermissions.length; i++) {
                Log.d(LOG_TAG, "## onRequestPermissionsResult(): " + aPermissions[i] + "=" + aGrantResults[i]);

                if (Manifest.permission.CAMERA.equals(aPermissions[i])) {
                    if (PackageManager.PERMISSION_GRANTED == aGrantResults[i]) {
                        Log.d(LOG_TAG, "## onRequestPermissionsResult(): CAMERA permission granted");
                        isCameraPermissionGranted = true;
                    } else {
                        Log.d(LOG_TAG, "## onRequestPermissionsResult(): CAMERA permission not granted");
                    }
                }
            }

            // the user allows to use to the camera.
            if (isCameraPermissionGranted) {
                Intent intent = new Intent(VectorRoomActivity.this, VectorMediasPickerActivity.class);
                intent.putExtra(VectorMediasPickerActivity.EXTRA_AVATAR_MODE, true);
                startActivityForResult(intent, REQUEST_ROOM_AVATAR_CODE);
            } else {
                launchRoomDetails(VectorRoomDetailsActivity.SETTINGS_TAB_INDEX);
            }
        } else if (aRequestCode == CommonActivityUtils.REQUEST_CODE_PERMISSION_TAKE_PHOTO) {
            boolean isCameraPermissionGranted = false;

            for (int i = 0; i < aPermissions.length; i++) {
                Log.d(LOG_TAG, "## onRequestPermissionsResult(): " + aPermissions[i] + "=" + aGrantResults[i]);

                if (Manifest.permission.CAMERA.equals(aPermissions[i])) {
                    if (PackageManager.PERMISSION_GRANTED == aGrantResults[i]) {
                        Log.d(LOG_TAG, "## onRequestPermissionsResult(): CAMERA permission granted");
                        isCameraPermissionGranted = true;
                    } else {
                        Log.d(LOG_TAG, "## onRequestPermissionsResult(): CAMERA permission not granted");
                    }
                }

                if (Manifest.permission.WRITE_EXTERNAL_STORAGE.equals(aPermissions[i])) {
                    if (PackageManager.PERMISSION_GRANTED == aGrantResults[i]) {
                        Log.d(LOG_TAG,
                                "## onRequestPermissionsResult(): WRITE_EXTERNAL_STORAGE permission granted");
                    } else {
                        Log.d(LOG_TAG,
                                "## onRequestPermissionsResult(): WRITE_EXTERNAL_STORAGE permission not granted");
                    }
                }
            }

            // Because external storage permission is not mandatory to launch the camera,
            // external storage permission is not tested.
            if (isCameraPermissionGranted) {
                launchCamera();
            } else {
                CommonActivityUtils.displayToast(this, getString(R.string.missing_permissions_warning));
            }
        } else if (aRequestCode == CommonActivityUtils.REQUEST_CODE_PERMISSION_AUDIO_IP_CALL) {
            if (CommonActivityUtils.onPermissionResultAudioIpCall(this, aPermissions, aGrantResults)) {
                startIpCall(false);
            }
        } else if (aRequestCode == CommonActivityUtils.REQUEST_CODE_PERMISSION_VIDEO_IP_CALL) {
            if (CommonActivityUtils.onPermissionResultVideoIpCall(this, aPermissions, aGrantResults)) {
                startIpCall(true);
            }
        } else {
            Log.w(LOG_TAG, "## onRequestPermissionsResult(): Unknown requestCode =" + aRequestCode);
        }
    }

    /**
     * Display UI buttons according to user input text.
     */
    private void manageSendMoreButtons() {
        boolean hasText = (mEditText.getText().length() > 0);
        mSendImageView.setImageResource(hasText ? R.drawable.ic_material_send_green : R.drawable.ic_material_file);
    }

    /**
     * Refresh the Account avatar
     */
    private void refreshSelfAvatar() {
        // sanity check
        if (null != mAvatarImageView) {
            VectorUtils.loadUserAvatar(this, mSession, mAvatarImageView, mSession.getMyUser());
        }
    }

    /**
     * Sanitize the display name.
     *
     * @param displayName the display name to sanitize
     * @return the sanitized display name
     */
    private static String sanitizeDisplayname(String displayName) {
        // sanity checks
        if (!TextUtils.isEmpty(displayName)) {
            final String ircPattern = " (IRC)";

            if (displayName.endsWith(ircPattern)) {
                displayName = displayName.substring(0, displayName.length() - ircPattern.length());
            }
        }

        return displayName;
    }

    /**
     * Insert an user displayname  in the message editor.
     *
     * @param text the text to insert.
     */
    public void insertUserDisplayNameInTextEditor(String text) {
        if (null != text) {
            if (TextUtils.equals(mSession.getMyUser().displayname, text)) {
                // current user
                if (TextUtils.isEmpty(mEditText.getText())) {
                    mEditText.setText(String.format("%s ", SlashComandsParser.CMD_EMOTE));
                    mEditText.setSelection(mEditText.getText().length());
                }
            } else {
                // another user
                if (TextUtils.isEmpty(mEditText.getText())) {
                    mEditText.append(sanitizeDisplayname(text) + ": ");
                } else {
                    mEditText.getText().insert(mEditText.getSelectionStart(), sanitizeDisplayname(text) + " ");
                }
            }
        }
    }

    /**
     * Insert a quote  in the message editor.
     *
     * @param quote the quote to insert.
     */
    public void insertQuoteInTextEditor(String quote) {
        if (!TextUtils.isEmpty(quote)) {
            if (TextUtils.isEmpty(mEditText.getText())) {
                mEditText.setText("");
                mEditText.append(quote);
            } else {
                mEditText.getText().insert(mEditText.getSelectionStart(), "\n" + quote);
            }
        }
    }

    //================================================================================
    // Notifications area management (... is typing and so on)
    //================================================================================

    /**
     * Track the cancel all click.
     */
    private class cancelAllClickableSpan extends ClickableSpan {
        @Override
        public void onClick(View widget) {
            mVectorMessageListFragment.deleteUnsentMessages();
            refreshNotificationsArea();
        }

        @Override
        public void updateDrawState(TextPaint ds) {
            super.updateDrawState(ds);
            ds.setColor(getResources().getColor(R.color.vector_fuchsia_color));
            ds.bgColor = 0;
            ds.setUnderlineText(true);
        }
    }

    /**
     * Track the resend all click.
     */
    private class resendAllClickableSpan extends ClickableSpan {
        @Override
        public void onClick(View widget) {
            mVectorMessageListFragment.resendUnsentMessages();
            refreshNotificationsArea();
        }

        @Override
        public void updateDrawState(TextPaint ds) {
            super.updateDrawState(ds);
            ds.setColor(getResources().getColor(R.color.vector_fuchsia_color));
            ds.bgColor = 0;
            ds.setUnderlineText(true);
        }
    }

    /**
     * Refresh the notifications area.
     */
    private void refreshNotificationsArea() {
        // sanity check
        // might happen when the application is logged out
        if ((null == mSession.getDataHandler()) || (null == mRoom) || (null != sRoomPreviewData)) {
            return;
        }

        int iconId = -1;
        int textColor = -1;
        boolean isAreaVisible = false;
        SpannableString text = new SpannableString("");
        boolean hasUnsentEvent = false;

        // remove any listeners
        mNotificationTextView.setOnClickListener(null);
        mNotificationIconImageView.setOnClickListener(null);

        //  no network
        if (!Matrix.getInstance(this).isConnected()) {
            isAreaVisible = true;
            iconId = R.drawable.error;
            textColor = R.color.vector_fuchsia_color;
            text = new SpannableString(getResources().getString(R.string.room_offline_notification));
        } else {
            List<Event> undeliveredEvents = mSession.getDataHandler().getStore()
                    .getUndeliverableEvents(mRoom.getRoomId());
            List<Event> unknownDeviceEvents = mSession.getDataHandler().getStore()
                    .getUnknownDeviceEvents(mRoom.getRoomId());

            boolean hasUndeliverableEvents = (null != undeliveredEvents) && (undeliveredEvents.size() > 0);
            boolean hasUnknownDeviceEvents = (null != unknownDeviceEvents) && (unknownDeviceEvents.size() > 0);

            if (hasUndeliverableEvents || hasUnknownDeviceEvents) {
                hasUnsentEvent = true;
                isAreaVisible = true;
                iconId = R.drawable.error;

                String cancelAll = getResources().getString(R.string.room_prompt_cancel);
                String resendAll = getResources().getString(R.string.room_prompt_resend);
                String message = getResources()
                        .getString(hasUnknownDeviceEvents ? R.string.room_unknown_devices_messages_notification
                                : R.string.room_unsent_messages_notification, resendAll, cancelAll);

                int cancelAllPos = message.indexOf(cancelAll);
                int resendAllPos = message.indexOf(resendAll);

                text = new SpannableString(message);

                // cancelAllPos should always be > 0 but a GA crash reported here
                if (cancelAllPos >= 0) {
                    text.setSpan(new cancelAllClickableSpan(), cancelAllPos, cancelAllPos + cancelAll.length(), 0);
                }

                // resendAllPos should always be > 0 but a GA crash reported here
                if (resendAllPos >= 0) {
                    text.setSpan(new resendAllClickableSpan(), resendAllPos, resendAllPos + resendAll.length(), 0);
                }

                mNotificationTextView.setMovementMethod(LinkMovementMethod.getInstance());
                textColor = R.color.vector_fuchsia_color;

            } else if ((null != mIsScrolledToTheBottom) && (!mIsScrolledToTheBottom)) {
                isAreaVisible = true;

                int unreadCount = 0;

                RoomSummary summary = mRoom.getDataHandler().getStore().getSummary(mRoom.getRoomId());

                if (null != summary) {
                    unreadCount = mRoom.getDataHandler().getStore().eventsCountAfter(mRoom.getRoomId(),
                            summary.getLatestReadEventId());
                }

                if (unreadCount > 0) {
                    iconId = R.drawable.newmessages;
                    textColor = R.color.vector_fuchsia_color;

                    if (unreadCount == 1) {
                        text = new SpannableString(
                                getResources().getString(R.string.room_new_message_notification));
                    } else {
                        text = new SpannableString(
                                getResources().getString(R.string.room_new_messages_notification, unreadCount));
                    }
                } else {
                    iconId = R.drawable.scrolldown;
                    textColor = R.color.vector_text_gray_color;

                    if (!TextUtils.isEmpty(mLatestTypingMessage)) {
                        text = new SpannableString(mLatestTypingMessage);
                    }
                }

                mNotificationTextView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        mVectorMessageListFragment.scrollToBottom(0);
                    }
                });

                mNotificationIconImageView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        mVectorMessageListFragment.scrollToBottom(0);
                    }
                });

            } else if (!TextUtils.isEmpty(mLatestTypingMessage)) {
                isAreaVisible = true;

                iconId = R.drawable.vector_typing;
                text = new SpannableString(mLatestTypingMessage);
                textColor = R.color.vector_text_gray_color;
            }
        }

        if (TextUtils.isEmpty(mEventId)) {
            mNotificationsArea.setVisibility(isAreaVisible ? View.VISIBLE : View.INVISIBLE);
        }

        if ((-1 != iconId) && (-1 != textColor)) {
            mNotificationIconImageView.setImageResource(iconId);
            mNotificationTextView.setText(text);
            mNotificationTextView.setTextColor(getResources().getColor(textColor));
        }

        //
        if (null != mResendUnsentMenuItem) {
            mResendUnsentMenuItem.setVisible(hasUnsentEvent);
        }

        if (null != mResendDeleteMenuItem) {
            mResendDeleteMenuItem.setVisible(hasUnsentEvent);
        }

        if (null != mSearchInRoomMenuItem) {
            // the server search does not work on encrypted rooms.
            mSearchInRoomMenuItem.setVisible(!mRoom.isEncrypted());
        }
    }

    /**
     * Refresh the call buttons display.
     */
    private void refreshCallButtons() {
        if ((null == sRoomPreviewData) && (null == mEventId) && canSendMessages()) {
            boolean isCallSupported = mRoom.canPerformCall() && mSession.isVoipCallSupported();
            IMXCall call = VectorCallViewActivity.getActiveCall();

            if (null == call) {
                mStartCallLayout.setVisibility(
                        (isCallSupported && (mEditText.getText().length() == 0)) ? View.VISIBLE : View.GONE);
                mStopCallLayout.setVisibility(View.GONE);
            } else {
                // ensure that the listener is defined once
                call.removeListener(mCallListener);
                call.addListener(mCallListener);

                IMXCall roomCall = mSession.mCallsManager.getCallWithRoomId(mRoom.getRoomId());

                mStartCallLayout.setVisibility(View.GONE);
                mStopCallLayout.setVisibility((call == roomCall) ? View.VISIBLE : View.GONE);
            }

            mVectorOngoingConferenceCallView.refresh();
        }
    }

    /**
     * Display the typing status in the notification area.
     */
    private void onRoomTypings() {
        mLatestTypingMessage = null;

        List<String> typingUsers = mRoom.getTypingUsers();

        if ((null != typingUsers) && (typingUsers.size() > 0)) {
            String myUserId = mSession.getMyUserId();

            // get the room member names
            ArrayList<String> names = new ArrayList<>();

            for (int i = 0; i < typingUsers.size(); i++) {
                RoomMember member = mRoom.getMember(typingUsers.get(i));

                // check if the user is known and not oneself
                if ((null != member) && !TextUtils.equals(myUserId, member.getUserId())
                        && (null != member.displayname)) {
                    names.add(member.displayname);
                }
            }

            // nothing to display ?
            if (0 == names.size()) {
                mLatestTypingMessage = null;
            } else if (1 == names.size()) {
                mLatestTypingMessage = String.format(this.getString(R.string.room_one_user_is_typing),
                        names.get(0));
            } else if (2 == names.size()) {
                mLatestTypingMessage = String.format(this.getString(R.string.room_two_users_are_typing),
                        names.get(0), names.get(1));
            } else if (names.size() > 2) {
                mLatestTypingMessage = String.format(this.getString(R.string.room_many_users_are_typing),
                        names.get(0), names.get(1));
            }
        }

        refreshNotificationsArea();
    }

    //================================================================================
    // expandable header management command
    //================================================================================

    /**
     * Refresh the collapsed or the expanded headers
     */
    private void updateActionBarTitleAndTopic() {
        setTitle();
        setTopic();
    }

    /**
     * Set the topic
     */
    private void setTopic() {
        String topic = null;

        if (null != mRoom) {
            topic = mRoom.getTopic();
        } else if ((null != sRoomPreviewData) && (null != sRoomPreviewData.getRoomState())) {
            topic = sRoomPreviewData.getRoomState().topic;
        }

        setTopic(topic);
    }

    /**
     * Set the topic.
     *
     * @param aTopicValue the new topic value
     */
    private void setTopic(String aTopicValue) {
        // in search mode, the topic is not displayed
        if (!TextUtils.isEmpty(mEventId)) {
            mActionBarCustomTopic.setVisibility(View.GONE);
        } else {
            // update the topic of the room header
            updateRoomHeaderTopic();

            // update the action bar topic anyway
            mActionBarCustomTopic.setText(aTopicValue);

            // set the visibility of topic on the custom action bar only
            // if header room view is gone, otherwise skipp it
            if (View.GONE == mRoomHeaderView.getVisibility()) {
                // topic is only displayed if its content is not empty
                if (TextUtils.isEmpty(aTopicValue)) {
                    mActionBarCustomTopic.setVisibility(View.GONE);
                } else {
                    mActionBarCustomTopic.setVisibility(View.VISIBLE);
                }
            }
        }
    }

    /**
     * Refresh the room avatar.
     */
    private void updateRoomHeaderAvatar() {
        if (null != mRoom) {
            VectorUtils.loadRoomAvatar(this, mSession, mActionBarHeaderRoomAvatar, mRoom);
        } else if (null != sRoomPreviewData) {
            String roomName = sRoomPreviewData.getRoomName();
            if (TextUtils.isEmpty(roomName)) {
                roomName = " ";
            }
            VectorUtils.loadUserAvatar(this, sRoomPreviewData.getSession(), mActionBarHeaderRoomAvatar,
                    sRoomPreviewData.getRoomAvatarUrl(), sRoomPreviewData.getRoomId(), roomName);
        }
    }

    /**
     * Create a custom action bar layout to process the room header view.
     * <p>
     * This action bar layout will contain a title, a topic and an arrow.
     * The arrow is updated (down/up) according to if the room header is
     * displayed or not.
     */
    private void setActionBarDefaultCustomLayout() {
        // binding the widgets of the custom view
        mActionBarCustomTitle = (TextView) findViewById(R.id.room_action_bar_title);
        mActionBarCustomTopic = (TextView) findViewById(R.id.room_action_bar_topic);
        mActionBarCustomArrowImageView = (ImageView) findViewById(R.id.open_chat_header_arrow);

        // custom header
        View headerTextsContainer = findViewById(R.id.header_texts_container);

        // add click listener on custom action bar to display/hide the header view
        mActionBarCustomArrowImageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (null != mRoomHeaderView) {
                    if (View.GONE == mRoomHeaderView.getVisibility()) {
                        enableActionBarHeader(SHOW_ACTION_BAR_HEADER);
                    } else {
                        enableActionBarHeader(HIDE_ACTION_BAR_HEADER);
                    }
                }
            }
        });

        headerTextsContainer.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (TextUtils.isEmpty(mEventId) && (null == sRoomPreviewData)) {
                    enableActionBarHeader(SHOW_ACTION_BAR_HEADER);
                }
            }
        });

        // add touch listener on the header view itself
        if (null != mRoomHeaderView) {
            mRoomHeaderView.setOnTouchListener(new View.OnTouchListener() {
                // last position
                private float mStartX;
                private float mStartY;

                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    if (event.getAction() == MotionEvent.ACTION_DOWN) {
                        mStartX = event.getX();
                        mStartY = event.getY();
                    } else if (event.getAction() == MotionEvent.ACTION_UP) {
                        float curX = event.getX();
                        float curY = event.getY();

                        float deltaX = curX - mStartX;
                        float deltaY = curY - mStartY;

                        // swipe up to hide room header
                        if ((Math.abs(deltaY) > Math.abs(deltaX)) && (deltaY < 0)) {
                            enableActionBarHeader(HIDE_ACTION_BAR_HEADER);
                        } else {
                            // wait the touch up to display the room settings page
                            launchRoomDetails(VectorRoomDetailsActivity.SETTINGS_TAB_INDEX);
                        }
                    }
                    return true;
                }
            });
        }
    }

    /**
     * Set the title value in the action bar and in the
     * room header layout
     */
    private void setTitle() {
        String titleToApply = mDefaultRoomName;
        if ((null != mSession) && (null != mRoom)) {
            titleToApply = VectorUtils.getRoomDisplayName(this, mSession, mRoom);

            if (TextUtils.isEmpty(titleToApply)) {
                titleToApply = mDefaultRoomName;
            }

            // in context mode, add search to the title.
            if (!TextUtils.isEmpty(mEventId)) {
                titleToApply = getResources().getText(R.string.search) + " : " + titleToApply;
            }
        } else if (null != sRoomPreviewData) {
            titleToApply = sRoomPreviewData.getRoomName();
        }

        // set action bar title
        if (null != mActionBarCustomTitle) {
            mActionBarCustomTitle.setText(titleToApply);
        } else {
            setTitle(titleToApply);
        }

        // set title in the room header (no matter if not displayed)
        if (null != mActionBarHeaderRoomName) {
            mActionBarHeaderRoomName.setText(titleToApply);
        }
    }

    /**
     * Update the UI content of the action bar header view
     */
    private void updateActionBarHeaderView() {
        // update room avatar content
        updateRoomHeaderAvatar();

        // update the room name
        if (null != mRoom) {
            mActionBarHeaderRoomName.setText(VectorUtils.getRoomDisplayName(this, mSession, mRoom));
        } else if (null != sRoomPreviewData) {
            mActionBarHeaderRoomName.setText(sRoomPreviewData.getRoomName());
        } else {
            mActionBarHeaderRoomName.setText("");
        }

        // update topic and members status
        updateRoomHeaderTopic();
        updateRoomHeaderMembersStatus();
    }

    private void updateRoomHeaderTopic() {
        if (null != mActionBarCustomTopic) {
            String value = null;

            if (null != mRoom) {
                value = mRoom.isReady() ? mRoom.getTopic() : mDefaultTopic;
            } else if ((null != sRoomPreviewData) && (null != sRoomPreviewData.getRoomState())) {
                value = sRoomPreviewData.getRoomState().topic;
            }

            // if topic value is empty, just hide the topic TextView
            if (TextUtils.isEmpty(value)) {
                mActionBarHeaderRoomTopic.setVisibility(View.GONE);
            } else {
                mActionBarHeaderRoomTopic.setVisibility(View.VISIBLE);
                mActionBarHeaderRoomTopic.setText(value);
            }
        }
    }

    /**
     * Tell if the user can send a message in this room.
     *
     * @return true if the user is allowed to send messages in this room.
     */
    private boolean canSendMessages() {
        boolean canSendMessage = false;

        if ((null != mRoom) && (null != mRoom.getLiveState())) {
            canSendMessage = true;
            PowerLevels powerLevels = mRoom.getLiveState().getPowerLevels();

            if (null != powerLevels) {
                canSendMessage = powerLevels.maySendMessage(mMyUserId);
            }
        }

        return canSendMessage;
    }

    /**
     * Check if the user can send a message in this room
     */
    private void checkSendEventStatus() {
        if ((null != mRoom) && (null != mRoom.getLiveState())) {
            boolean canSendMessage = canSendMessages();
            mSendingMessagesLayout.setVisibility(canSendMessage ? View.VISIBLE : View.GONE);
            mCanNotPostTextView.setVisibility(!canSendMessage ? View.VISIBLE : View.GONE);
        }
    }

    /**
     * Display the active members count / members count in the expendable header.
     */
    private void updateRoomHeaderMembersStatus() {
        if (null != mActionBarHeaderActiveMembers) {
            // refresh only if the action bar is hidden
            if (mActionBarCustomTitle.getVisibility() == View.GONE) {

                if ((null != mRoom) || (null != sRoomPreviewData)) {
                    // update the members status: "active members"/"members"
                    int joinedMembersCount = 0;
                    int activeMembersCount = 0;

                    RoomState roomState = (null != sRoomPreviewData) ? sRoomPreviewData.getRoomState()
                            : mRoom.getState();

                    if (null != roomState) {
                        Collection<RoomMember> members = roomState.getDisplayableMembers();

                        for (RoomMember member : members) {
                            if (TextUtils.equals(member.membership, RoomMember.MEMBERSHIP_JOIN)) {
                                joinedMembersCount++;

                                User user = mSession.getDataHandler().getStore().getUser(member.getUserId());

                                if ((null != user) && user.isActive()) {
                                    activeMembersCount++;
                                }
                            }
                        }

                        // in preview mode, the room state might be a publicRoom
                        // so try to use the public room info.
                        if ((roomState instanceof PublicRoom) && (0 == joinedMembersCount)) {
                            activeMembersCount = joinedMembersCount = ((PublicRoom) roomState).numJoinedMembers;
                        }

                        boolean displayInvite = TextUtils.isEmpty(mEventId) && (null == sRoomPreviewData)
                                && (1 == joinedMembersCount);

                        if (displayInvite) {
                            mActionBarHeaderActiveMembers.setVisibility(View.GONE);
                            mActionBarHeaderInviteMemberView.setVisibility(View.VISIBLE);
                        } else {
                            mActionBarHeaderInviteMemberView.setVisibility(View.GONE);
                            String text = null;
                            if (null != sRoomPreviewData) {
                                if (joinedMembersCount == 1) {
                                    text = getResources().getString(R.string.room_title_one_member);
                                } else if (joinedMembersCount > 0) {
                                    text = getResources().getString(R.string.room_title_members,
                                            joinedMembersCount);
                                }
                            } else {
                                text = getString(R.string.room_header_active_members, activeMembersCount,
                                        joinedMembersCount);
                            }

                            if (!TextUtils.isEmpty(text)) {
                                mActionBarHeaderActiveMembers.setText(text);
                                mActionBarHeaderActiveMembers.setVisibility(View.VISIBLE);
                            } else {
                                mActionBarHeaderActiveMembers.setVisibility(View.GONE);
                            }
                        }
                    } else {
                        mActionBarHeaderActiveMembers.setVisibility(View.GONE);
                        mActionBarHeaderActiveMembers.setVisibility(View.GONE);
                    }
                }

            } else {
                mActionBarHeaderActiveMembers.setVisibility(View.GONE);
            }
        }
    }

    /**
     * Show or hide the action bar header view according to aIsHeaderViewDisplayed
     *
     * @param aIsHeaderViewDisplayed true to show the header view, false to hide
     */
    private void enableActionBarHeader(boolean aIsHeaderViewDisplayed) {

        mIsHeaderViewDisplayed = aIsHeaderViewDisplayed;
        if (SHOW_ACTION_BAR_HEADER == aIsHeaderViewDisplayed) {

            InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
            imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);

            // hide the name and the topic in the action bar.
            // these items are hidden when the header view is opened
            mActionBarCustomTitle.setVisibility(View.GONE);
            mActionBarCustomTopic.setVisibility(View.GONE);

            // update the UI content of the action bar header
            updateActionBarHeaderView();
            // set the arrow to up
            mActionBarCustomArrowImageView.setImageResource(R.drawable.ic_arrow_drop_up_white);
            // enable the header view to make it visible
            mRoomHeaderView.setVisibility(View.VISIBLE);
            mToolbar.setBackgroundColor(Color.TRANSPARENT);
        } else {
            // hide the room header only if it is displayed
            if (View.VISIBLE == mRoomHeaderView.getVisibility()) {
                // show the name and the topic in the action bar.
                mActionBarCustomTitle.setVisibility(View.VISIBLE);
                // if the topic is empty, do not show it
                if (!TextUtils.isEmpty(mActionBarCustomTopic.getText())) {
                    mActionBarCustomTopic.setVisibility(View.VISIBLE);
                }

                // update title and topic (action bar)
                updateActionBarTitleAndTopic();

                // hide the action bar header view and reset the arrow image (arrow reset to down)
                mActionBarCustomArrowImageView.setImageResource(R.drawable.ic_arrow_drop_down_white);
                mRoomHeaderView.setVisibility(View.GONE);
                mToolbar.setBackgroundColor(ContextCompat.getColor(this, R.color.vector_green_color));
            }
        }
    }

    //================================================================================
    // Room preview management
    //================================================================================

    @Override
    public RoomPreviewData getRoomPreviewData() {
        return sRoomPreviewData;
    }

    /**
     * Manage the room preview buttons area
     */
    private void manageRoomPreview() {
        if (null != sRoomPreviewData) {
            mRoomPreviewLayout.setVisibility(View.VISIBLE);

            TextView invitationTextView = (TextView) findViewById(R.id.room_preview_invitation_textview);
            TextView subInvitationTextView = (TextView) findViewById(R.id.room_preview_subinvitation_textview);

            Button joinButton = (Button) findViewById(R.id.button_join_room);
            Button declineButton = (Button) findViewById(R.id.button_decline);

            final RoomEmailInvitation roomEmailInvitation = sRoomPreviewData.getRoomEmailInvitation();

            String roomName = sRoomPreviewData.getRoomName();
            if (TextUtils.isEmpty(roomName)) {
                roomName = " ";
            }

            Log.d(LOG_TAG, "Preview the room " + sRoomPreviewData.getRoomId());

            // if the room already exists
            if (null != mRoom) {
                Log.d(LOG_TAG, "manageRoomPreview : The room is known");

                String inviter = "";

                if (null != roomEmailInvitation) {
                    inviter = roomEmailInvitation.inviterName;
                }

                if (TextUtils.isEmpty(inviter)) {
                    Collection<RoomMember> members = mRoom.getActiveMembers();
                    for (RoomMember member : members) {
                        if (TextUtils.equals(member.membership, RoomMember.MEMBERSHIP_JOIN)) {
                            inviter = TextUtils.isEmpty(member.displayname) ? member.getUserId()
                                    : member.displayname;
                        }
                    }
                }

                invitationTextView
                        .setText(getResources().getString(R.string.room_preview_invitation_format, inviter));

                declineButton.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Log.d(LOG_TAG, "The user clicked on decline.");

                        setProgressVisibility(View.VISIBLE);

                        mRoom.leave(new ApiCallback<Void>() {
                            @Override
                            public void onSuccess(Void info) {
                                Log.d(LOG_TAG, "The invitation is rejected");
                                onDeclined();
                            }

                            private void onError(String errorMessage) {
                                Log.d(LOG_TAG, "The invitation rejection failed " + errorMessage);
                                CommonActivityUtils.displayToast(VectorRoomActivity.this, errorMessage);
                                setProgressVisibility(View.GONE);
                            }

                            @Override
                            public void onNetworkError(Exception e) {
                                onError(e.getLocalizedMessage());
                            }

                            @Override
                            public void onMatrixError(MatrixError e) {
                                onError(e.getLocalizedMessage());
                            }

                            @Override
                            public void onUnexpectedError(Exception e) {
                                onError(e.getLocalizedMessage());
                            }
                        });
                    }
                });

            } else {
                if ((null != roomEmailInvitation) && !TextUtils.isEmpty(roomEmailInvitation.email)) {
                    invitationTextView.setText(getResources().getString(R.string.room_preview_invitation_format,
                            roomEmailInvitation.inviterName));
                    subInvitationTextView.setText(getResources()
                            .getString(R.string.room_preview_unlinked_email_warning, roomEmailInvitation.email));
                } else {
                    invitationTextView
                            .setText(getResources().getString(R.string.room_preview_try_join_an_unknown_room,
                                    TextUtils.isEmpty(sRoomPreviewData.getRoomName())
                                            ? getResources().getString(
                                                    R.string.room_preview_try_join_an_unknown_room_default)
                                            : roomName));

                    // the room preview has some messages
                    if ((null != sRoomPreviewData.getRoomResponse())
                            && (null != sRoomPreviewData.getRoomResponse().messages)) {
                        subInvitationTextView.setText(
                                getResources().getString(R.string.room_preview_room_interactions_disabled));
                    }
                }

                declineButton.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Log.d(LOG_TAG, "The invitation is declined (unknown room)");

                        sRoomPreviewData = null;
                        VectorRoomActivity.this.finish();
                    }
                });
            }

            joinButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Log.d(LOG_TAG, "The user clicked on Join.");

                    Room room = sRoomPreviewData.getSession().getDataHandler()
                            .getRoom(sRoomPreviewData.getRoomId());

                    String signUrl = null;

                    if (null != roomEmailInvitation) {
                        signUrl = roomEmailInvitation.signUrl;
                    }

                    setProgressVisibility(View.VISIBLE);

                    room.joinWithThirdPartySigned(sRoomPreviewData.getRoomIdOrAlias(), signUrl,
                            new ApiCallback<Void>() {
                                @Override
                                public void onSuccess(Void info) {
                                    onJoined();
                                }

                                private void onError(String errorMessage) {
                                    CommonActivityUtils.displayToast(VectorRoomActivity.this, errorMessage);
                                    setProgressVisibility(View.GONE);
                                }

                                @Override
                                public void onNetworkError(Exception e) {
                                    onError(e.getLocalizedMessage());
                                }

                                @Override
                                public void onMatrixError(MatrixError e) {
                                    onError(e.getLocalizedMessage());
                                }

                                @Override
                                public void onUnexpectedError(Exception e) {
                                    onError(e.getLocalizedMessage());
                                }
                            });

                }
            });

            enableActionBarHeader(SHOW_ACTION_BAR_HEADER);

        } else {
            mRoomPreviewLayout.setVisibility(View.GONE);
        }
    }

    /**
     * The room invitation has been declined
     */
    private void onDeclined() {
        if (null != sRoomPreviewData) {
            VectorRoomActivity.this.finish();
            sRoomPreviewData = null;
        }
    }

    /**
     * the room has been joined
     */
    private void onJoined() {
        if (null != sRoomPreviewData) {
            HashMap<String, Object> params = new HashMap<>();

            processDirectMessageRoom();

            params.put(VectorRoomActivity.EXTRA_MATRIX_ID, mSession.getMyUserId());
            params.put(VectorRoomActivity.EXTRA_ROOM_ID, sRoomPreviewData.getRoomId());

            if (null != sRoomPreviewData.getEventId()) {
                params.put(VectorRoomActivity.EXTRA_EVENT_ID, sRoomPreviewData.getEventId());
            }

            // clear the activity stack to home activity
            Intent intent = new Intent(VectorRoomActivity.this, VectorHomeActivity.class);
            intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP
                    | Intent.FLAG_ACTIVITY_NEW_TASK);

            intent.putExtra(VectorHomeActivity.EXTRA_JUMP_TO_ROOM_PARAMS, params);
            VectorRoomActivity.this.startActivity(intent);

            sRoomPreviewData = null;
        }
    }

    /**
     * If the joined room was tagged as "direct chat room", it is required to update the
     * room as a "direct chat room" (account_data)
     */
    private void processDirectMessageRoom() {
        Room room = sRoomPreviewData.getSession().getDataHandler().getRoom(sRoomPreviewData.getRoomId());
        if ((null != room) && (room.isDirectChatInvitation())) {
            String myUserId = mSession.getMyUserId();
            Collection<RoomMember> members = mRoom.getMembers();

            if (2 == members.size()) {
                String participantUserId;

                // test if room is already seen as "direct message"
                if (mSession.getDirectChatRoomIdsList().indexOf(sRoomPreviewData.getRoomId()) < 0) {
                    for (RoomMember member : members) {
                        // search for the second participant
                        if (!member.getUserId().equals(myUserId)) {
                            participantUserId = member.getUserId();
                            CommonActivityUtils.setToggleDirectMessageRoom(mSession, sRoomPreviewData.getRoomId(),
                                    participantUserId, this, mDirectMessageListener);
                            break;
                        }
                    }
                } else {
                    Log.d(LOG_TAG, "## processDirectMessageRoom(): attempt to add an already direct message room");
                }
            }
        }
    }

    //================================================================================
    // Room header clicks management.
    //================================================================================

    /**
     * Update the avatar from the data provided the medias picker.
     *
     * @param aData the provided data.
     */
    private void onActivityResultRoomAvatarUpdate(final Intent aData) {
        // sanity check
        if (null == mSession) {
            return;
        }

        Uri thumbnailUri = VectorUtils.getThumbnailUriFromIntent(this, aData, mSession.getMediasCache());

        if (null != thumbnailUri) {
            setProgressVisibility(View.VISIBLE);

            // save the bitmap URL on the server
            ResourceUtils.Resource resource = ResourceUtils.openResource(this, thumbnailUri, null);
            if (null != resource) {
                mSession.getMediasCache().uploadContent(resource.mContentStream, null, resource.mMimeType, null,
                        new MXMediaUploadListener() {
                            @Override
                            public void onUploadError(String uploadId, int serverResponseCode,
                                    String serverErrorMessage) {
                                Log.e(LOG_TAG, "Fail to upload the avatar");
                            }

                            @Override
                            public void onUploadComplete(final String uploadId, final String contentUri) {
                                VectorRoomActivity.this.runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        Log.d(LOG_TAG, "The avatar has been uploaded, update the room avatar");
                                        mRoom.updateAvatarUrl(contentUri, new ApiCallback<Void>() {

                                            private void onDone(String message) {
                                                if (!TextUtils.isEmpty(message)) {
                                                    CommonActivityUtils.displayToast(VectorRoomActivity.this,
                                                            message);
                                                }

                                                setProgressVisibility(View.GONE);
                                                updateRoomHeaderAvatar();
                                            }

                                            @Override
                                            public void onSuccess(Void info) {
                                                onDone(null);
                                            }

                                            @Override
                                            public void onNetworkError(Exception e) {
                                                onDone(e.getLocalizedMessage());
                                            }

                                            @Override
                                            public void onMatrixError(MatrixError e) {
                                                onDone(e.getLocalizedMessage());
                                            }

                                            @Override
                                            public void onUnexpectedError(Exception e) {
                                                onDone(e.getLocalizedMessage());
                                            }
                                        });
                                    }
                                });
                            }
                        });
            }
        }
    }

    /**
     * The user clicks on the room title.
     * Assume he wants to update it.
     */
    private void onRoomTitleClick() {
        LayoutInflater inflater = LayoutInflater.from(this);

        AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);

        View dialogView = inflater.inflate(R.layout.dialog_text_edittext, null);
        alertDialogBuilder.setView(dialogView);

        TextView titleText = (TextView) dialogView.findViewById(R.id.dialog_title);
        titleText.setText(getResources().getString(R.string.room_info_room_name));

        final EditText textInput = (EditText) dialogView.findViewById(R.id.dialog_edit_text);
        textInput.setText(mRoom.getLiveState().name);

        // set dialog message
        alertDialogBuilder.setCancelable(false)
                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        setProgressVisibility(View.VISIBLE);

                        mRoom.updateName(textInput.getText().toString(), new ApiCallback<Void>() {

                            private void onDone(String message) {
                                if (!TextUtils.isEmpty(message)) {
                                    CommonActivityUtils.displayToast(VectorRoomActivity.this, message);
                                }

                                setProgressVisibility(View.GONE);
                                updateActionBarTitleAndTopic();
                            }

                            @Override
                            public void onSuccess(Void info) {
                                onDone(null);
                            }

                            @Override
                            public void onNetworkError(Exception e) {
                                onDone(e.getLocalizedMessage());
                            }

                            @Override
                            public void onMatrixError(MatrixError e) {
                                onDone(e.getLocalizedMessage());
                            }

                            @Override
                            public void onUnexpectedError(Exception e) {
                                onDone(e.getLocalizedMessage());
                            }
                        });
                    }
                }).setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.cancel();
                    }
                });

        // create alert dialog
        AlertDialog alertDialog = alertDialogBuilder.create();

        // show it
        alertDialog.show();
    }

    /**
     * The user clicks on the room topic.
     * Assume he wants to update it.
     */
    private void onRoomTopicClick() {
        LayoutInflater inflater = LayoutInflater.from(this);

        AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);

        View dialogView = inflater.inflate(R.layout.dialog_text_edittext, null);
        alertDialogBuilder.setView(dialogView);

        TextView titleText = (TextView) dialogView.findViewById(R.id.dialog_title);
        titleText.setText(getResources().getString(R.string.room_info_room_topic));

        final EditText textInput = (EditText) dialogView.findViewById(R.id.dialog_edit_text);
        textInput.setText(mRoom.getLiveState().topic);

        // set dialog message
        alertDialogBuilder.setCancelable(false)
                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        setProgressVisibility(View.VISIBLE);

                        mRoom.updateTopic(textInput.getText().toString(), new ApiCallback<Void>() {

                            private void onDone(String message) {
                                if (!TextUtils.isEmpty(message)) {
                                    CommonActivityUtils.displayToast(VectorRoomActivity.this, message);
                                }

                                setProgressVisibility(View.GONE);
                                updateActionBarTitleAndTopic();
                            }

                            @Override
                            public void onSuccess(Void info) {
                                onDone(null);
                            }

                            @Override
                            public void onNetworkError(Exception e) {
                                onDone(e.getLocalizedMessage());
                            }

                            @Override
                            public void onMatrixError(MatrixError e) {
                                onDone(e.getLocalizedMessage());
                            }

                            @Override
                            public void onUnexpectedError(Exception e) {
                                onDone(e.getLocalizedMessage());
                            }
                        });
                    }
                }).setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.cancel();
                    }
                });

        // create alert dialog
        AlertDialog alertDialog = alertDialogBuilder.create();

        // show it
        alertDialog.show();
    }

    /**
     * Add click management on expanded header
     */
    private void addRoomHeaderClickListeners() {
        // tap on the expanded room avatar
        View roomAvatarView = findViewById(R.id.room_avatar);

        if (null != roomAvatarView) {
            roomAvatarView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // sanity checks : reported by GA
                    if ((null != mRoom) && (null != mRoom.getLiveState())) {
                        if (CommonActivityUtils.isPowerLevelEnoughForAvatarUpdate(mRoom, mSession)) {
                            // need to check if the camera permission has been granted
                            if (CommonActivityUtils.checkPermissions(
                                    CommonActivityUtils.REQUEST_CODE_PERMISSION_ROOM_DETAILS,
                                    VectorRoomActivity.this)) {
                                Intent intent = new Intent(VectorRoomActivity.this,
                                        VectorMediasPickerActivity.class);
                                intent.putExtra(VectorMediasPickerActivity.EXTRA_AVATAR_MODE, true);
                                startActivityForResult(intent, REQUEST_ROOM_AVATAR_CODE);
                            }
                        } else {
                            launchRoomDetails(VectorRoomDetailsActivity.SETTINGS_TAB_INDEX);
                        }
                    }
                }
            });
        }

        // tap on the room name to update it
        View titleText = findViewById(R.id.action_bar_header_room_title);

        if (null != titleText) {
            titleText.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // sanity checks : reported by GA
                    if ((null != mRoom) && (null != mRoom.getLiveState())) {
                        boolean canUpdateTitle = false;
                        PowerLevels powerLevels = mRoom.getLiveState().getPowerLevels();

                        if (null != powerLevels) {
                            int powerLevel = powerLevels.getUserPowerLevel(mSession.getMyUserId());
                            canUpdateTitle = powerLevel >= powerLevels
                                    .minimumPowerLevelForSendingEventAsStateEvent(Event.EVENT_TYPE_STATE_ROOM_NAME);
                        }

                        if (canUpdateTitle) {
                            onRoomTitleClick();
                        } else {
                            launchRoomDetails(VectorRoomDetailsActivity.SETTINGS_TAB_INDEX);
                        }
                    }
                }
            });
        }

        // tap on the room name to update it
        View topicText = findViewById(R.id.action_bar_header_room_topic);

        if (null != topicText) {
            topicText.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // sanity checks : reported by GA
                    if ((null != mRoom) && (null != mRoom.getLiveState())) {
                        boolean canUpdateTopic = false;
                        PowerLevels powerLevels = mRoom.getLiveState().getPowerLevels();

                        if (null != powerLevels) {
                            int powerLevel = powerLevels.getUserPowerLevel(mSession.getMyUserId());
                            canUpdateTopic = powerLevel >= powerLevels
                                    .minimumPowerLevelForSendingEventAsStateEvent(Event.EVENT_TYPE_STATE_ROOM_NAME);
                        }

                        if (canUpdateTopic) {
                            onRoomTopicClick();
                        } else {
                            launchRoomDetails(VectorRoomDetailsActivity.SETTINGS_TAB_INDEX);
                        }
                    }
                }
            });
        }

        View membersListTextView = findViewById(R.id.action_bar_header_room_members);

        if (null != membersListTextView) {
            membersListTextView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    launchRoomDetails(VectorRoomDetailsActivity.PEOPLE_TAB_INDEX);
                }
            });
        }
    }

    private static final String E2E_WARNINGS_PREFERENCES = "E2E_WARNINGS_PREFERENCES";

    /**
     * Display an e2e alert for the first opened room.
     */
    private void displayE2eRoomAlert() {
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);

        if (!preferences.contains(E2E_WARNINGS_PREFERENCES) && (null != mRoom) && mRoom.isEncrypted()) {
            SharedPreferences.Editor editor = preferences.edit();
            editor.putBoolean(E2E_WARNINGS_PREFERENCES, false);
            editor.commit();

            android.support.v7.app.AlertDialog.Builder builder = new android.support.v7.app.AlertDialog.Builder(
                    this);
            builder.setTitle(R.string.room_e2e_alert_title);
            builder.setMessage(R.string.room_e2e_alert_message);
            builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    // NOP
                }
            });
            builder.create().show();
        }
    }
}