org.jitsi.android.gui.call.VideoCallActivity.java Source code

Java tutorial

Introduction

Here is the source code for org.jitsi.android.gui.call.VideoCallActivity.java

Source

/*
 * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jitsi.android.gui.call;

import java.beans.*;
import java.util.*;

import android.annotation.*;
import android.content.*;
import android.graphics.Color;
import android.media.*;
import android.os.*;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentManager;
import android.view.*;
import android.view.Menu; // Disambiguation
import android.view.MenuItem; // Disambiguation
import android.widget.*;

import org.jitsi.R;
import org.jitsi.android.*;
import org.jitsi.android.gui.call.notification.*;
import org.jitsi.android.gui.controller.*;
import org.jitsi.android.gui.util.*;
import org.jitsi.android.gui.widgets.*;
import org.jitsi.service.neomedia.*;
import org.jitsi.service.osgi.*;

import net.java.sip.communicator.service.gui.call.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.service.protocol.media.*;
import net.java.sip.communicator.util.*;
import net.java.sip.communicator.util.call.*;
import net.java.sip.communicator.util.call.CallPeerAdapter;

/**
 * The <tt>VideoCallActivity</tt> corresponds the call screen.
 *
 * @author Yana Stamcheva
 * @author Pawel Domas
 */
public class VideoCallActivity extends OSGiActivity implements CallPeerRenderer, CallRenderer, CallChangeListener,
        PropertyChangeListener, ZrtpInfoDialog.SasVerificationListener, AutoHideController.AutoHideListener {
    /**
     * The logger
     */
    private static final Logger logger = Logger.getLogger(VideoCallActivity.class);

    /**
     * Tag name for fragment that handles proximity sensor in order to turn
     * the screen on and off.
     */
    private static final String PROXIMITY_FRAGMENT_TAG = "proximity";

    /**
     * Tag name that identifies video handler fragment.
     */
    private static final String VIDEO_FRAGMENT_TAG = "video";

    /**
     * Tag name that identifies call timer fragment.
     */
    private static final String TIMER_FRAGMENT_TAG = "call_timer";

    /**
     * Tag name that identifies call control buttons auto hide controller
     * fragment.
     */
    private static final String AUTO_HIDE_TAG = "auto_hide";

    /**
     * The delay for hiding the call control buttons, after the call has started
     */
    private static final long AUTO_HIDE_DELAY = 5000;

    /**
     * Tag for call volume control fragment.
     */
    private static final String VOLUME_CTRL_TAG = "call_volume_ctrl";

    /**
     * The call peer adapter that gives us access to all call peer events.
     */
    private CallPeerAdapter callPeerAdapter;

    /**
     * The corresponding call.
     */
    private Call call;

    /**
     * The {@link CallConference} instance depicted by this <tt>CallPanel</tt>.
     */
    private CallConference callConference;

    /**
     * Flag indicates if the shutdown Thread has been started
     */
    private volatile boolean finishing = false;

    /**
     * The call identifier managed by {@link CallManager}
     */
    private String callIdentifier;

    /**
     * The zrtp SAS verification toast controller.
     */
    private LegacyClickableToastCtrl sasToastController;

    /**
     * Auto-hide controller fragment for call control buttons.
     * It is attached when remote video covers most part of the screen.
     */
    private AutoHideController autoHide;

    /**
     * Call volume control fragment instance.
     */
    private CallVolumeCtrlFragment volControl;

    /**
     * Instance holds call state to be displayed in <tt>CallEnded</tt> fragment.
     * Call objects will be no longer available after the call has ended.
     */
    static CallStateHolder callState = new CallStateHolder();

    /**
     * Called when the activity is starting. Initializes the corresponding
     * call interface.
     *
     * @param savedInstanceState If the activity is being re-initialized after
     * previously being shut down then this Bundle contains the data it most
     * recently supplied in onSaveInstanceState(Bundle).
     * Note: Otherwise it is null.
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.video_call);

        this.callIdentifier = getIntent().getExtras().getString(CallManager.CALL_IDENTIFIER);

        call = CallManager.getActiveCall(callIdentifier);

        if (call == null) {
            logger.error("There's no call with id: " + callIdentifier);
            return;
        }

        callConference = call.getConference();

        initSpeakerphoneButton();
        initMicrophoneView();
        initHangupView();

        // Registers as the call state listener
        call.addCallChangeListener(this);

        View toastView = findViewById(R.id.clickable_toast);
        View.OnClickListener toastclickHandler = new View.OnClickListener() {
            public void onClick(View v) {
                showZrtpInfoDialog();
                sasToastController.hideToast(true);
            }
        };

        if (AndroidUtils.hasAPI(11)) {
            sasToastController = new ClickableToastController(toastView, toastclickHandler);
        } else {
            sasToastController = new LegacyClickableToastCtrl(toastView, toastclickHandler);
        }

        if (savedInstanceState == null) {
            VideoHandlerFragment videoFragment;
            if (AndroidUtils.hasAPI(18)) {
                videoFragment = new VideoHandlerFragmentAPI18();
            } else {
                videoFragment = new VideoHandlerFragment();
            }

            volControl = new CallVolumeCtrlFragment();

            /**
             * Adds fragment that turns on and off the screen when proximity
             * sensor detects FAR/NEAR distance.
             */
            getSupportFragmentManager().beginTransaction().add(volControl, VOLUME_CTRL_TAG)
                    .add(new ProximitySensorFragment(), PROXIMITY_FRAGMENT_TAG)
                    /* Adds the fragment that handles video display logic */
                    .add(videoFragment, VIDEO_FRAGMENT_TAG)
                    /* Adds the fragment that handles call duration logic */
                    .add(new CallTimerFragment(), TIMER_FRAGMENT_TAG).commit();
        } else {
            FragmentManager fragmentManager = getSupportFragmentManager();

            // Retrieve restored auto hide fragment
            autoHide = (AutoHideController) fragmentManager.findFragmentByTag(AUTO_HIDE_TAG);

            volControl = (CallVolumeCtrlFragment) fragmentManager.findFragmentByTag(VOLUME_CTRL_TAG);
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        if (sasToastController != null)
            sasToastController.onSaveInstanceState(outState);
        super.onSaveInstanceState(outState);
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        if (sasToastController != null)
            sasToastController.onRestoreInstanceState(savedInstanceState);
    }

    /**
     * Initializes the hangup button view.
     */
    private void initHangupView() {
        ImageView hangupView = (ImageView) findViewById(R.id.callHangupButton);

        hangupView.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                // Start the hang up Thread, Activity will be closed later 
                // on call ended event
                CallManager.hangupCall(call);
            }
        });
    }

    /**
     * Called on call ended event. Runs on separate thread to release the EDT
     * Thread and preview surface can be hidden effectively.
     */
    private void doFinishActivity() {
        if (finishing)
            return;

        finishing = true;

        new Thread(new Runnable() {
            public void run() {
                // Waits for camera to be stopped
                getVideoFragment().ensureCameraClosed();

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        callState.callDuration = ViewUtil.getTextViewValue(findViewById(android.R.id.content),
                                R.id.callTime);
                        callState.callEnded = true;

                        // Remove video fragment
                        if (getVideoFragment() != null) {
                            getSupportFragmentManager().beginTransaction().remove(getVideoFragment()).commit();
                        }
                        // Remove auto hide fragment
                        ensureAutoHideFragmentDetached();

                        getSupportFragmentManager().beginTransaction()
                                .replace(android.R.id.content, new CallEnded()).commit();
                    }
                });
            }
        }).start();
    }

    /**
     * Initializes the microphone button view.
     */
    private void initMicrophoneView() {
        final ImageView microphoneButton = (ImageView) findViewById(R.id.callMicrophoneButton);

        microphoneButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                CallManager.setMute(call, !isMuted());
            }
        });
        microphoneButton.setOnLongClickListener(new View.OnLongClickListener() {
            public boolean onLongClick(View view) {
                DialogFragment newFragment = VolumeControlDialog.createInputVolCtrlDialog();
                newFragment.show(getSupportFragmentManager(), "vol_ctrl_dialog");
                return true;
            }
        });
    }

    /**
     * Returns <tt>true</tt> if call is currently muted.
     *
     * @return <tt>true</tt> if call is currently muted.
     */
    private boolean isMuted() {
        return CallManager.isMute(call);
    }

    private void updateMuteStatus() {
        runOnUiThread(new Runnable() {
            public void run() {
                doUpdateMuteStatus();
            }
        });
    }

    private void doUpdateMuteStatus() {
        final ImageView microphoneButton = (ImageView) findViewById(R.id.callMicrophoneButton);

        if (isMuted()) {
            microphoneButton.setBackgroundColor(0x50000000);
            microphoneButton.setImageResource(R.drawable.callmicrophonemute);
        } else {
            microphoneButton.setBackgroundColor(Color.TRANSPARENT);
            microphoneButton.setImageResource(R.drawable.callmicrophone);
        }
    }

    /**
     * Initializes speakerphone button.
     */
    private void initSpeakerphoneButton() {
        View speakerphoneButton = findViewById(R.id.speakerphoneButton);
        speakerphoneButton.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                onCallVolumeClicked(v);
                return true;
            }
        });
    }

    /**
     * Fired when call volume control button is clicked.
     * @param v the call volume control <tt>View</tt>.
     */
    public void onCallVolumeClicked(View v) {
        // Create and show the dialog.
        DialogFragment newFragment = VolumeControlDialog.createOutputVolCtrlDialog();
        newFragment.show(getSupportFragmentManager(), "vol_ctrl_dialog");
    }

    /**
     * Fired when speakerphone button is clicked.
     * @param v the speakerphone button <tt>View</tt>.
     */
    public void onSpeakerphoneClicked(View v) {
        AudioManager audioManager = JitsiApplication.getAudioManager();
        audioManager.setSpeakerphoneOn(!audioManager.isSpeakerphoneOn());
        updateSpeakerphoneStatus();
    }

    /**
     * Updates speakerphone button status.
     */
    private void updateSpeakerphoneStatus() {
        final ImageView speakerPhoneButton = (ImageView) findViewById(R.id.speakerphoneButton);

        if (JitsiApplication.getAudioManager().isSpeakerphoneOn()) {
            speakerPhoneButton.setBackgroundColor(0x50000000);
        } else {
            speakerPhoneButton.setBackgroundColor(Color.TRANSPARENT);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        /**
         * The call to:
         * setVolumeControlStream(AudioManager.STREAM_VOICE_CALL)
         * doesn't work when notification was being played during this Activity
         * creation, so the buttons must be captured and the voice call level
         * will be manipulated programmatically.
         */
        int action = event.getAction();
        int keyCode = event.getKeyCode();
        switch (keyCode) {
        case KeyEvent.KEYCODE_VOLUME_UP:
            if (action == KeyEvent.ACTION_UP) {
                volControl.onKeyVolUp();
            }
            return true;
        case KeyEvent.KEYCODE_VOLUME_DOWN:
            if (action == KeyEvent.ACTION_DOWN) {
                volControl.onKeyVolDown();
            }
            return true;
        default:
            return super.dispatchKeyEvent(event);
        }
    }

    /**
     * Reinitialize the <tt>Activity</tt> to reflect current call status.
     */
    @Override
    protected void onResume() {
        super.onResume();

        // Clears the in call notification
        if (CallNotificationManager.get().isNotificationRunning(callIdentifier)) {
            CallNotificationManager.get().stopNotification(callIdentifier);

        }
        // Call already ended or not found
        if (call == null)
            return;

        // Registers as the call state listener
        call.addCallChangeListener(this);

        // Checks if call peer has video component
        Iterator<? extends CallPeer> peers = call.getCallPeers();
        if (peers.hasNext()) {
            CallPeer callPeer = peers.next();
            addCallPeerUI(callPeer);
        } else {
            if (!callState.callEnded) {
                logger.error("There aren't any peers in the call");
                finish();
            }
            return;
        }

        doUpdateHoldStatus();
        doUpdateMuteStatus();
        updateSpeakerphoneStatus();
        initSecurityStatus();
    }

    /**
     * Called when this <tt>Activity</tt> is paused(hidden).
     * Releases all listeners and leaves the in call notification if the call is
     * in progress.
     */
    @Override
    protected void onPause() {
        super.onPause();

        if (call == null)
            return;

        call.removeCallChangeListener(this);

        if (callPeerAdapter != null) {
            Iterator<? extends CallPeer> callPeerIter = call.getCallPeers();
            if (callPeerIter.hasNext()) {
                removeCallPeerUI(callPeerIter.next());
            }
            callPeerAdapter.dispose();
            callPeerAdapter = null;
        }

        if (call.getCallState() != CallState.CALL_ENDED) {
            leaveNotification();
        }
    }

    /**
     * Leaves the in call notification.
     */
    private void leaveNotification() {
        if (!AndroidUtils.hasAPI(11)) {
            // TODO: fix in call notifications for sdk < 11
            logger.warn("In call notifications not supported prior SDK 11");
            return;
        }

        CallNotificationManager.get().showCallNotification(this, callIdentifier);
    }

    /**
     * Sets the peer name.
     *
     * @param name the name of the call peer
     */
    public void setPeerName(final String name) {
        // ActionBar is not support prior 3.0
        if (!AndroidUtils.hasAPI(11))
            return;

        callState.callPeerName = name;

        runOnUiThread(new Runnable() {
            public void run() {
                ActionBarUtil.setTitle(VideoCallActivity.this,
                        getResources().getString(R.string.service_gui_CALL_WITH) + ": ");
                ActionBarUtil.setSubtitle(VideoCallActivity.this, name);
            }
        });
    }

    /**
     * Sets the peer image.
     *
     * @param image the avatar of the call peer
     */
    public void setPeerImage(byte[] image) {

    }

    /**
     * Sets the peer state.
     *
     * @param oldState the old peer state
     * @param newState the new peer state
     * @param stateString the state of the call peer
     */
    public void setPeerState(CallPeerState oldState, CallPeerState newState, final String stateString) {
        runOnUiThread(new Runnable() {
            public void run() {
                TextView statusName = (TextView) findViewById(R.id.callStatus);

                statusName.setText(stateString);
            }
        });
    }

    /**
     * Ensures that auto hide fragment is added and started.
     */
    void ensureAutoHideFragmentAttached() {
        if (autoHide != null)
            return;

        this.autoHide = AutoHideController.getInstance(R.id.buttonContainer, AUTO_HIDE_DELAY);

        getSupportFragmentManager().beginTransaction().add(autoHide, AUTO_HIDE_TAG).commit();
    }

    /**
     * Removes the auto hide fragment, so that call control buttons will be
     * always visible from now on.
     */
    public void ensureAutoHideFragmentDetached() {
        if (autoHide != null) {
            autoHide.show();

            getSupportFragmentManager().beginTransaction().remove(autoHide).commit();

            autoHide = null;
        }
    }

    /**
     * Shows (or cancels) the auto hide fragment.
     */
    @Override
    public void onUserInteraction() {
        super.onUserInteraction();

        if (autoHide != null)
            autoHide.show();
    }

    /**
     * Returns <tt>CallVolumeCtrlFragment</tt> if it exists or <tt>null</tt>
     * otherwise.
     * @return <tt>CallVolumeCtrlFragment</tt> if it exists or <tt>null</tt>
     *         otherwise.
     */
    public CallVolumeCtrlFragment getVolCtrlFragment() {
        return volControl;
    }

    public void setErrorReason(final String reason) {
        logger.info("Error reason: " + reason);

        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                callState.errorReason = reason;

                TextView errorReason = (TextView) findViewById(R.id.callErrorReason);
                if (errorReason != null) {
                    errorReason.setText(reason);
                    errorReason.setVisibility(View.VISIBLE);
                }
            }
        });
    }

    public void setMute(boolean isMute) {
        // Just invoke mute UI refresh
        updateMuteStatus();
    }

    /**
     * Method mapped to hold button view on click event
     *
     * @param holdButtonView the button view that has been clicked
     */
    public void onHoldButtonClicked(View holdButtonView) {
        CallManager.putOnHold(call, !isOnHold());
    }

    private boolean isOnHold() {
        boolean onHold = false;
        Iterator<? extends CallPeer> peers = call.getCallPeers();
        if (peers.hasNext()) {
            CallPeerState peerState = call.getCallPeers().next().getState();
            onHold = CallPeerState.ON_HOLD_LOCALLY.equals(peerState)
                    || CallPeerState.ON_HOLD_MUTUALLY.equals(peerState);
        } else {
            logger.warn("No peer belongs to call: " + call.toString());
        }

        return onHold;
    }

    public void setOnHold(boolean isOnHold) {
    }

    /**
     * Updates on hold button to represent it's actual state
     */
    private void updateHoldStatus() {
        runOnUiThread(new Runnable() {
            public void run() {
                doUpdateHoldStatus();
            }
        });
    }

    /**
     * Updates on hold button to represent it's actual state.
     * Called from {@link #updateHoldStatus()}.
     */
    private void doUpdateHoldStatus() {
        final ImageView holdButton = (ImageView) findViewById(R.id.callHoldButton);

        if (isOnHold()) {
            holdButton.setBackgroundColor(0x50000000);
        } else {
            holdButton.setBackgroundColor(Color.TRANSPARENT);
        }
    }

    public void printDTMFTone(char dtmfChar) {

    }

    public CallRenderer getCallRenderer() {
        return this;
    }

    public void setLocalVideoVisible(final boolean isVisible) {
        // It can not be hidden here, because the preview surface will be
        // destroyed and camera recording system will crash     
    }

    private VideoHandlerFragment getVideoFragment() {
        return (VideoHandlerFragment) getSupportFragmentManager().findFragmentByTag("video");
    }

    public boolean isLocalVideoVisible() {
        return getVideoFragment().isLocalVideoVisible();
    }

    public Call getCall() {
        return call;
    }

    public CallPeerRenderer getCallPeerRenderer(CallPeer callPeer) {
        return this;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.video_call_menu, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.low_resolution:
            return true;
        case R.id.high_resolution:
            return true;
        case R.id.call_info_item:
            showCallInfoDialog();
            return true;
        case R.id.call_zrtp_info_item:
            showZrtpInfoDialog();
            return true;
        default:
            return super.onOptionsItemSelected(item);
        }
    }

    /**
     * Displays technical call information dialog.
     */
    private void showCallInfoDialog() {
        CallInfoDialogFragment callInfo = CallInfoDialogFragment
                .newInstance(getIntent().getStringExtra(CallManager.CALL_IDENTIFIER));

        callInfo.show(getSupportFragmentManager(), "callinfo");
    }

    /**
     * Displays ZRTP call information dialog.
     */
    private void showZrtpInfoDialog() {
        ZrtpInfoDialog zrtpInfo = ZrtpInfoDialog
                .newInstance(getIntent().getStringExtra(CallManager.CALL_IDENTIFIER));

        zrtpInfo.show(getSupportFragmentManager(), "zrtpinfo");
    }

    public void propertyChange(PropertyChangeEvent evt) {
        /*
         * If a Call is added to or removed from the CallConference depicted
         * by this CallPanel, an update of the view from its model will most
         * likely be required.
         */
        if (evt.getPropertyName().equals(CallConference.CALLS))
            onCallConferenceEventObject(evt);
    }

    public void callPeerAdded(CallPeerEvent evt) {
        CallPeer callPeer = evt.getSourceCallPeer();

        addCallPeerUI(callPeer);

        onCallConferenceEventObject(evt);
    }

    public void callPeerRemoved(CallPeerEvent evt) {
        CallPeer callPeer = evt.getSourceCallPeer();

        if (callPeerAdapter != null) {
            callPeer.addCallPeerListener(callPeerAdapter);
            callPeer.addCallPeerSecurityListener(callPeerAdapter);
            callPeer.addPropertyChangeListener(callPeerAdapter);
        }

        setPeerState(callPeer.getState(), callPeer.getState(), callPeer.getState().getLocalizedStateString());

        onCallConferenceEventObject(evt);
    }

    public void callStateChanged(CallChangeEvent evt) {
        onCallConferenceEventObject(evt);
    }

    /**
     * Invoked by {@link #callConferenceListener} to notify this instance about
     * an <tt>EventObject</tt> related to the <tt>CallConference</tt> depicted
     * by this <tt>CallPanel</tt>, the <tt>Call</tt>s participating in it,
     * the <tt>CallPeer</tt>s associated with them, the
     * <tt>ConferenceMember</tt>s participating in any telephony conferences
     * organized by them, etc. In other words, notifies this instance about
     * any change which may cause an update to be required so that this view
     * i.e. <tt>CallPanel</tt> depicts the current state of its model i.e.
     * {@link #callConference}.
     *
     * @param ev the <tt>EventObject</tt> this instance is being notified
     * about.
     */
    private void onCallConferenceEventObject(EventObject ev) {
        /*
         * The main task is to invoke updateViewFromModel() in order to make
         * sure that this view depicts the current state of its model.
         */

        try {
            /*
             * However, we seem to be keeping track of the duration of the call
             * (i.e. the telephony conference) in the user interface. Stop the
             * Timer which ticks the duration of the call as soon as the
             * telephony conference depicted by this instance appears to have
             * ended. The situation will very likely occur when a Call is
             * removed from the telephony conference or a CallPeer is removed
             * from a Call.
             */
            boolean tryStopCallTimer = false;

            if (ev instanceof CallPeerEvent) {
                tryStopCallTimer = (CallPeerEvent.CALL_PEER_REMOVED == ((CallPeerEvent) ev).getEventID());
            } else if (ev instanceof PropertyChangeEvent) {
                PropertyChangeEvent pcev = (PropertyChangeEvent) ev;

                tryStopCallTimer = (CallConference.CALLS.equals(pcev) && (pcev.getOldValue() instanceof Call)
                        && (pcev.getNewValue() == null));
            }

            if (tryStopCallTimer && (callConference.isEnded() || callConference.getCallPeerCount() == 0)) {
                stopCallTimer();
                doFinishActivity();
            }
        } finally {
            updateViewFromModel(ev);
        }
    }

    /**
     * Gets the <tt>CallTimerFragment</tt>.
     * @return the <tt>CallTimerFragment</tt>.
     */
    private CallTimerFragment getCallTimerFragment() {
        return (CallTimerFragment) getSupportFragmentManager().findFragmentByTag(TIMER_FRAGMENT_TAG);
    }

    /**
     * Starts the timer that counts call duration.
     */
    public void startCallTimer() {
        if (getCallTimerFragment() != null)
            getCallTimerFragment().startCallTimer();
    }

    /**
     * Stops the timer that counts call duration.
     */
    public void stopCallTimer() {
        if (getCallTimerFragment() != null)
            getCallTimerFragment().stopCallTimer();
    }

    /**
     * Returns <code>true</code> if the call timer has been started, otherwise
     * returns <code>false</code>.
     * @return <code>true</code> if the call timer has been started, otherwise
     * returns <code>false</code>
     */
    public boolean isCallTimerStarted() {
        if (getCallTimerFragment() != null) {
            return getCallTimerFragment().isCallTimerStarted();
        }
        return false;
    }

    /**
     * {@inheritDoc}
     */
    public void onSasVerificationChanged(boolean isVerified) {
        doUpdatePadlockStatus(true, isVerified);
    }

    private void addCallPeerUI(CallPeer callPeer) {
        callPeerAdapter = new CallPeerAdapter(callPeer, this);

        callPeer.addCallPeerListener(callPeerAdapter);
        callPeer.addCallPeerSecurityListener(callPeerAdapter);
        callPeer.addPropertyChangeListener(callPeerAdapter);

        setPeerState(null, callPeer.getState(), callPeer.getState().getLocalizedStateString());
        setPeerName(callPeer.getDisplayName());

        getCallTimerFragment().callPeerAdded(callPeer);
    }

    /**
     * Removes given <tt>callPeer</tt> from UI.
     *
     * @param callPeer the {@link CallPeer} to be removed from UI.
     */
    private void removeCallPeerUI(CallPeer callPeer) {
        callPeer.removeCallPeerListener(callPeerAdapter);
        callPeer.removeCallPeerSecurityListener(callPeerAdapter);
        callPeer.removePropertyChangeListener(callPeerAdapter);
    }

    private void updateViewFromModel(EventObject ev) {
    }

    public void updateHoldButtonState() {
        updateHoldStatus();
    }

    public void dispose() {
    }

    public void securityNegotiationStarted(CallPeerSecurityNegotiationStartedEvent securityStartedEvent) {
    }

    /**
     * Initializes current security status displays.
     */
    private void initSecurityStatus() {
        boolean isSecure = false;
        boolean isVerified = false;
        ZrtpControl zrtpCtrl = null;

        Iterator<? extends CallPeer> callPeers = call.getCallPeers();
        if (callPeers.hasNext()) {
            CallPeer cpCandidate = callPeers.next();
            if (cpCandidate instanceof MediaAwareCallPeer<?, ?, ?>) {
                MediaAwareCallPeer<?, ?, ?> mediaAwarePeer = (MediaAwareCallPeer<?, ?, ?>) cpCandidate;
                SrtpControl srtpCtrl = mediaAwarePeer.getMediaHandler().getEncryptionMethod(MediaType.AUDIO);
                isSecure = srtpCtrl != null && srtpCtrl.getSecureCommunicationStatus();

                if (srtpCtrl instanceof ZrtpControl) {
                    zrtpCtrl = (ZrtpControl) srtpCtrl;
                    isVerified = zrtpCtrl.isSecurityVerified();
                } else {
                    isVerified = true;
                }
            }
        }

        // Protocol name label
        ViewUtil.setTextViewValue(findViewById(android.R.id.content), R.id.security_protocol,
                zrtpCtrl != null ? "zrtp" : "");

        doUpdatePadlockStatus(isSecure, isVerified);
    }

    /**
     * Updates padlock status text, icon and it's background color.
     *
     * @param isSecure <tt>true</tt> if the call is secured.
     * @param isVerified <tt>true</tt> if zrtp SAS string is verified.
     */
    @SuppressLint("ResourceAsColor")
    private void doUpdatePadlockStatus(boolean isSecure, boolean isVerified) {
        if (isSecure) {
            if (isVerified) {
                // Security on
                setPadlockColor(R.color.green_padlock);
                setPadlockSecure(true);
            } else {
                // Security pending
                setPadlockColor(R.color.orange_padlock);
                setPadlockSecure(true);
            }
        } else {
            // Security off
            setPadlockColor(R.color.red_padlock);
            setPadlockSecure(false);
        }
    }

    /**
     * Sets the security padlock background color.
     *
     * @param colorId the color resource id that will be used.
     */
    private void setPadlockColor(int colorId) {
        View padlockGroup = findViewById(R.id.security_group);
        int color = getResources().getColor(colorId);
        padlockGroup.setBackgroundColor(color);
    }

    /**
     * Updates padlock icon based on security status.
     *
     * @param isSecure <tt>true</tt> if the call is secure.
     */
    private void setPadlockSecure(boolean isSecure) {
        ViewUtil.setImageViewIcon(findViewById(android.R.id.content), R.id.security_padlock,
                isSecure ? R.drawable.secure_on : R.drawable.secure_off);
    }

    /**
     * {@inheritDoc}
     */
    public void securityPending() {
        runOnUiThread(new Runnable() {
            public void run() {
                doUpdatePadlockStatus(false, false);
            }
        });
    }

    /**
     * {@inheritDoc}
     */
    public void securityTimeout(CallPeerSecurityTimeoutEvent evt) {

    }

    /**
     * {@inheritDoc}
     */
    public void setSecurityPanelVisible(boolean visible) {
    }

    /**
     * {@inheritDoc}
     */
    public void securityOff(CallPeerSecurityOffEvent evt) {
        runOnUiThread(new Runnable() {
            public void run() {
                doUpdatePadlockStatus(false, false);
            }
        });
    }

    /**
     * {@inheritDoc}
     */
    public void securityOn(final CallPeerSecurityOnEvent evt) {
        runOnUiThread(new Runnable() {
            public void run() {
                SrtpControl srtpCtrl = evt.getSecurityController();
                ZrtpControl zrtpControl = null;
                if (srtpCtrl instanceof ZrtpControl) {
                    zrtpControl = (ZrtpControl) srtpCtrl;
                }

                boolean isVerified = zrtpControl != null && zrtpControl.isSecurityVerified();

                doUpdatePadlockStatus(zrtpControl != null, isVerified);

                // Protocol name label
                ViewUtil.setTextViewValue(findViewById(android.R.id.content), R.id.security_protocol,
                        zrtpControl != null ? "zrtp" : "sdes");

                if (!isVerified && zrtpControl != null) {
                    String toastMsg = getString(R.string.service_gui_security_VERIFY_TOAST);
                    sasToastController.showToast(false, toastMsg);
                }
            }
        });
    }

    /**
     * Creates new video call intent for given <tt>callIdentifier</tt>.
     *
     * @param parent the parent <tt>Context</tt> that will be used to start new
     * <tt>Activity</tt>.
     * @param callIdentifier the call ID managed by {@link CallManager}.
     *
     * @return new video call <tt>Intent</tt> parametrized with given
     * <tt>callIdentifier</tt>.
     */
    static public Intent createVideoCallIntent(Context parent, String callIdentifier) {
        Intent videoCallIntent = new Intent(parent, VideoCallActivity.class);

        videoCallIntent.putExtra(CallManager.CALL_IDENTIFIER, callIdentifier);

        VideoHandlerFragment.wasVideoEnabled = false;

        return videoCallIntent;
    }

    /**
     * Updates views alignment which depend on call control buttons group
     * visibility state.
     *
     * {@inheritDoc}
     */
    @Override
    public void onAutoHideStateChanged(AutoHideController source, int visibility) {
        getVideoFragment().updateCallInfoMargin();
    }

    static class CallStateHolder {
        //Call call;
        //CallPeer callPeer;

        String callPeerName = "";
        String callDuration = "";
        String errorReason = "";
        boolean callEnded = false;
    }
}