/*
 * Copyright (C) 2015 AVM GmbH <info@avm.de>
 * Copyright (C) 2009 The Sipdroid Open Source Project
 * 
 * This file is part of Sipdroid (http://www.sipdroid.org)
 * 
 * Sipdroid is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This source code is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this source code; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package de.avm.android.fritzapp.sipua.ui;

import java.lang.ref.WeakReference;

import org.sipdroid.media.RtpStreamReceiver;
import de.avm.android.fritzapp.R;
import de.avm.android.fritzapp.com.ComSettingsChecker;
import de.avm.android.fritzapp.com.discovery.BoxInfo;
import de.avm.android.fritzapp.gui.Dialpad;
import de.avm.android.fritzapp.gui.SlidingDrawer;
import de.avm.android.fritzapp.gui.TamPreference;
import de.avm.android.fritzapp.sipua.UserAgent;
import de.avm.android.fritzapp.sipua.phone.Call;
import de.avm.android.fritzapp.sipua.phone.CallerInfo;
import de.avm.android.fritzapp.sipua.phone.CallerInfoAsyncQuery;
import de.avm.android.fritzapp.sipua.phone.Connection;
import de.avm.android.fritzapp.sipua.phone.ContactsAsyncHelper;
import de.avm.android.fritzapp.sipua.phone.Phone;
import de.avm.android.fritzapp.sipua.phone.PhoneUtils;
import de.avm.android.fritzapp.util.BuildHelper;
import de.avm.android.fritzapp.util.PhoneNumberHelper;
import de.avm.fundamentals.logger.FileLog;

import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Chronometer;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

public class InCallScreen extends CallScreen
		implements CallerInfoAsyncQuery.OnQueryCompleteListener
{
	final static String TAG = "InCallScreen";

	final static int MSG_ANSWER = 1;
	final static int MSG_ANSWER_SPEAKER = 2;
	final static int MSG_FINISH = 3;
	final static int MSG_HANGUP = 5;
	final static int MSG_HEADSETPLUG = 6;
	
	final int SCREEN_OFF_TIMEOUT = 12000;
	
	Phone ccPhone;
	int oldtimeout;
    private Chronometer mElapsedTime;
    private TextView mActionLabel;
    private TextView mLoopbackLabel;
    private ImageView mActionImage;
    private ImageView mActionImageHDOverlay;
    private TextView mName;
    private TextView mNumber;
    private TextView mPort;
    private Button mCallNow;
    private Button mHangup;
    private ImageButton mSlideDialpad;
    private ImageButton mReject;
    private ImageButton mSpeaker;
    private View mOptionButtonsGroup = null;
    private ImageButton mMute;
	private Dialpad mDialpad = null;
	private SendDtmfAsyncTask mDtmfSender = null;
	private boolean mTamAllreadySeen = false;

    // Track the state for the caller info.
    private ContactsAsyncHelper.ImageTracker mPhotoTracker;

    private HeadsetReceiver mHeadsetReceiver = new HeadsetReceiver();
    
	void screenOff(boolean off) {
        ContentResolver cr = getContentResolver();
        
        if (off) {
        	if (oldtimeout == 0) {
        		oldtimeout = Settings.System.getInt(cr, Settings.System.SCREEN_OFF_TIMEOUT, 60000);
	        	Settings.System.putInt(cr, Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_OFF_TIMEOUT);
        	}
        } else {
        	if (oldtimeout == 0 && Settings.System.getInt(cr, Settings.System.SCREEN_OFF_TIMEOUT, 60000) == SCREEN_OFF_TIMEOUT)
        		oldtimeout = 60000;
        	if (oldtimeout != 0) {
	        	Settings.System.putInt(cr, Settings.System.SCREEN_OFF_TIMEOUT, oldtimeout);
        		oldtimeout = 0;
        	}
        }
	}
	
	@Override
	public void onPause() {
		super.onPause();
    	if (!Sipdroid.release) FileLog.i("SipUA:", "on pause");
    	switch (Receiver.call_state)
    	{
	    	case UserAgent.UA_STATE_IDLE:
	    		displayMainCallStatus();
				mHandler.sendEmptyMessageDelayed(MSG_FINISH, 2000);
	    		break;
    	}
		screenOff(false);
		if (mElapsedTime != null) mElapsedTime.stop();
	}

	@Override
	public void finish()
	{
		Connection conn = Receiver.ccConn;
		if ((conn != null) && !conn.isIncoming() &&
				!Sipdroid.mBackToMainActivity)
		{
			// after an outgoing call don't fall back to the contact
			// or call log because it is too easy to dial accidentally from there
			try { startActivity(Receiver.createHomeIntent()); }
			catch (Exception e) { }
		}
		super.finish();
	}

	@Override
	public void onResume() {
		super.onResume();
    	if (!Sipdroid.release) FileLog.i("SipUA:","on resume");
		switch (Receiver.call_state) {
		case UserAgent.UA_STATE_INCOMING_CALL:
			if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(Sipdroid.PREF_AUTOON, false))
				mHandler.sendEmptyMessageDelayed(MSG_ANSWER, 1000); 
			else if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(Sipdroid.PREF_AUTOONDEMAND, false))
// ADO: no hands free, delayed only
//				mHandler.sendEmptyMessageDelayed(MSG_ANSWER_SPEAKER, 10000);
				mHandler.sendEmptyMessageDelayed(MSG_ANSWER, 10000);
			break;
		case UserAgent.UA_STATE_INCALL:
		    screenOff(true);
			break;
		case UserAgent.UA_STATE_IDLE:
			if (!mHandler.hasMessages(MSG_FINISH))
				finish();
			break;
		}

		if (!mTamAllreadySeen && (mDialpad != null))
		{
			if (isInTamCall())
			{
				mTamAllreadySeen = true;
				// dialpad and speaker in call with TAM
				mDialpad.open();
				if ((mSpeaker.getVisibility() == View.VISIBLE) &&
						(RtpStreamReceiver.speakermode != AudioManager.MODE_NORMAL))
					mSpeaker.performClick();
			}
			else mDialpad.close();
		}
		displayMainCallStatus();
	}
	
    private static class InCallScreenHandler extends Handler
    {
    	WeakReference<InCallScreen> mParentRef;
    	
    	public InCallScreenHandler(InCallScreen parent)
    	{
    		super();
    		mParentRef = new WeakReference<InCallScreen>(parent);
    	}
    	
    	public void handleMessage(Message msg)
    	{
    		InCallScreen parent = mParentRef.get();
    		if (parent != null)
    		{
	    		switch (msg.what)
	    		{
		    		case MSG_ANSWER:
		        		if (Receiver.call_state == UserAgent.UA_STATE_INCOMING_CALL)
		        			parent.answer();
		        		break;
		    		
		    		case MSG_ANSWER_SPEAKER:
		        		if (Receiver.call_state == UserAgent.UA_STATE_INCOMING_CALL)
		        		{
		        			parent.answer();
		    				Receiver.engine(parent)
		    					.speaker(AudioManager.MODE_NORMAL);
		        		}
		        		break;
		    		
		    		case MSG_FINISH:
		    			parent.finish();
		    			break;
		    		
		    		case MSG_HANGUP:
		        		if (Receiver.call_state == UserAgent.UA_STATE_INCALL)
		        			parent.mHangup.performClick();
		    			break;
		    			
		    		case MSG_HEADSETPLUG:
		    			parent.updateSpeakerButton();
		    			break;
	    		}
    		}
    	}
    };
    InCallScreenHandler mHandler = new InCallScreenHandler(this);
    
    @Override
	public void onCreate(Bundle icicle) {
		super.onCreate(icicle);
		FileLog.d(TAG, "onCreate()");
		
		android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY);

		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.incall);
		
        mElapsedTime = (Chronometer)findViewById(R.id.Duration);
        mActionLabel = (TextView)findViewById(R.id.ActionLabel);
        mLoopbackLabel = (TextView)findViewById(R.id.LoopbackLabel);
        mActionImage = (ImageView)findViewById(R.id.ActionImage);
        mActionImageHDOverlay = (ImageView)findViewById(R.id.ActionImageHDOverlay);
        mName = (TextView)findViewById(R.id.Name);
        mNumber = (TextView)findViewById(R.id.Number);
        mPort = (TextView)findViewById(R.id.Port);

        // Dialpad
		mDialpad = (Dialpad)findViewById(R.id.dtmf_dialer);
		mDialpad.setInitiallyOpen(false);
		mDialpad.setHint(R.string.dtmf_hint);
		mSlideDialpad = ((ImageButton)findViewById(R.id.SlideDialpad));
		mDialpad.setOnDrawerCloseListener(new SlidingDrawer.OnDrawerCloseListener()
		{
			public void onDrawerClosed()
			{
				mSlideDialpad.setImageResource(R.drawable.btn_dialpadopen);
				SendDtmfAsyncTask sender = mDtmfSender;
				if (sender != null)
				{
					mDtmfSender = null;
					sender.close();
				}
			}
		});
		mDialpad.setOnDrawerOpenListener(new SlidingDrawer.OnDrawerOpenListener()
		{
			public void onDrawerOpened()
			{
				mDialpad.setText("");
				mSlideDialpad.setImageResource(R.drawable.btn_dialpadclose);
				if (mDtmfSender == null)
				{
					mDtmfSender = (SendDtmfAsyncTask)new SendDtmfAsyncTask()
							.execute((Integer[])null);
				}
			}
		});
		mDialpad.setOnDtmfDigitListener(new Dialpad.OnDtmfDigitListener()
		{
			public void onDtmfDigit(char digit, int dtmfTone)
			{
				SendDtmfAsyncTask sender = mDtmfSender;
				if (sender != null) sender.put(digit, dtmfTone);
			}
		});
        
		// Footer Buttons
		mSlideDialpad.setOnClickListener(new OnClickListener()
		{
			public void onClick(View v)
			{
				mDialpad.animateToggle();
			}
		});
        mCallNow = (Button)findViewById(R.id.CallNow);
        mCallNow.setOnClickListener(new OnClickListener()
		{
			public void onClick(View v)
			{
				// prevent from clicking it again, because answer() works
				// asychronous and calling it twice leads to SIP-busy to all
				// incoming calls in future
				mCallNow.setEnabled(false);
				answer();
			}
		});
        mHangup = (Button)findViewById(R.id.Hangup);
        mHangup.setOnClickListener(new OnClickListener()
		{
			public void onClick(View v)
			{
				// prevent from clicking it again, because rejectcall() works
				// asychronous and calling it twice leads to SIP-busy to all
				// incoming calls in future
				mHangup.setEnabled(false);

				SendDtmfAsyncTask sender = mDtmfSender;
				if (sender != null) sender.cancel();
				Receiver.engine(InCallScreen.this).rejectcall();
			}
		});
        mReject = (ImageButton)findViewById(R.id.Reject);
        mReject.setOnClickListener(new OnClickListener()
		{
			public void onClick(View v)
			{
				reject();      
			}
		});
        mSpeaker = (ImageButton)findViewById(R.id.Speaker);
        mSpeaker.setOnClickListener(new OnClickListener()
		{
			public void onClick(View v)
			{
				if(BuildHelper.handsFreeCallingModeSupported()) {
					Receiver.engine(InCallScreen.this)
							.speaker((RtpStreamReceiver.speakermode == AudioManager.MODE_NORMAL) ?
									AudioManager.MODE_IN_CALL : AudioManager.MODE_NORMAL);
					Receiver.updateProximity();
					updateSpeakerButton();
				} else {
					Toast.makeText(getApplicationContext(), R.string.hint_speakermode_unsupported, Toast.LENGTH_SHORT).show();
					return;					
				}
			}
		});
        
        // options buttons
        mOptionButtonsGroup = findViewById(R.id.Menu);
        mMute = (ImageButton)findViewById(R.id.Mute);
        mMute.setOnClickListener(new OnClickListener()
		{
			public void onClick(View v)
			{
				toggleMute();
			}
		});

    	mHeadsetReceiver.register(); 
		
        displayOnHoldCallStatus(ccPhone, null);
        displayOngoingCallStatus(ccPhone, null);
        
        // Have the WindowManager filter out touch events that are "too fat".
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES);

        mPhotoTracker = new ContactsAsyncHelper.ImageTracker();
    }

    @Override
    public void onDestroy()
	{
		super.onDestroy();
		mHeadsetReceiver.unregister();
	}
    
	@Override
	public boolean onPrepareOptionsMenu(Menu menu) {
		boolean result = super.onPrepareOptionsMenu(menu);

		if (Receiver.call_state == UserAgent.UA_STATE_INCALL || Receiver.call_state == UserAgent.UA_STATE_HOLD) {
//			menu.findItem(HOLD_MENU_ITEM).setVisible(true);
			menu.findItem(MUTE_MENU_ITEM).setVisible(true);
//			menu.findItem(TRANSFER_MENU_ITEM).setVisible(true);
		} else {
//			menu.findItem(HOLD_MENU_ITEM).setVisible(false);
			menu.findItem(MUTE_MENU_ITEM).setVisible(false);
//			menu.findItem(TRANSFER_MENU_ITEM).setVisible(false);
		}
		
		return result;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		boolean result = super.onOptionsItemSelected(item);
		switch (item.getItemId()) {
//		case HOLD_MENU_ITEM:
//			Receiver.engine(this).togglehold();
//			break;
//
//		case TRANSFER_MENU_ITEM:
//			transfer();
//			break;
			
		case MUTE_MENU_ITEM:
			toggleMute();
			break;
		}

		return result;
	}
	
	protected void toggleMute()
	{
		if(BuildHelper.muteSupported())
		{
			Receiver.engine(this).togglemute();
			updateMuteButton();
		}
		else
			Toast.makeText(getApplicationContext(),
					R.string.hint_mute_unsupported, Toast.LENGTH_SHORT).show();
	}

	public void reject()
	{
		if (Receiver.ccCall != null)
		{
			Receiver.stopRingtone();
			Receiver.ccCall.setState(Call.State.DISCONNECTED);
			displayMainCallStatus();
			SendDtmfAsyncTask sender = mDtmfSender;
			if (sender != null) sender.cancel();
			if ((mDialpad != null) && mDialpad.isOpened()) mDialpad.close();
		}
        new Thread(new Runnable()
        {
			public void run()
			{
				try
				{
					Receiver.engine(InCallScreen.this).rejectcall();
				}
				catch(Exception e)
				{
					FileLog.w(TAG, "Failed to reject call", e);
				}
			}
		}).start();   	
    }
	
	public void answer()
	{
        new Thread(new Runnable()
        {
			public void run()
			{
				try
				{
					Receiver.engine(InCallScreen.this).answercall();
				}
				catch(Exception e)
				{
					FileLog.w(TAG, "Failed to answer call", e);
				}
			}
		}).start();   
		if (Receiver.ccCall != null)
		{
			Receiver.stopRingtone();
			Receiver.ccCall.setState(Call.State.ACTIVE);
			Receiver.ccCall.base = SystemClock.elapsedRealtime();
			displayMainCallStatus();
		}
	}
	

	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
        switch (keyCode) {
        case KeyEvent.KEYCODE_CALL:
        	switch (Receiver.call_state) {
        	case UserAgent.UA_STATE_INCOMING_CALL:
        		answer();
        		break;
        	case UserAgent.UA_STATE_INCALL:
        	case UserAgent.UA_STATE_HOLD:
       			Receiver.engine(this).togglehold();
       			break;
        	}
            // consume KEYCODE_CALL so PhoneWindow doesn't do anything with it
            return true;

        case KeyEvent.KEYCODE_CAMERA:
            // Disable the CAMERA button while in-call since it's too
            // easy to press accidentally.
        	return true;
        	
        case KeyEvent.KEYCODE_VOLUME_DOWN:
        case KeyEvent.KEYCODE_VOLUME_UP:
        	if (Receiver.call_state == UserAgent.UA_STATE_INCOMING_CALL) {
        		Receiver.stopRingtone();
        		return true;
        	}
        	RtpStreamReceiver.adjust(keyCode);
        	return true;
        }
        return super.onKeyDown(keyCode, event);
	}
	
	@Override
	public boolean onKeyUp(int keyCode, KeyEvent event) {
		switch (keyCode) {
	        case KeyEvent.KEYCODE_VOLUME_DOWN:
	        case KeyEvent.KEYCODE_VOLUME_UP:
        	return true;
		}
		Receiver.pstn_time = 0;
		return super.onKeyUp(keyCode, event);
	}

	@Override
	public void onBackPressed ()
	{
    	if ((mDialpad != null) && mDialpad.isOpened())
    	{
			SendDtmfAsyncTask sender = mDtmfSender;
			if (sender != null) sender.cancel();
    		mDialpad.animateClose();
    	}
    	else if (Receiver.call_state == UserAgent.UA_STATE_INCOMING_CALL)
    	{
    		reject();
    	}
    	else if ((Receiver.call_state == UserAgent.UA_STATE_IDLE) &&
    			!mHandler.hasMessages(MSG_FINISH))
    	{
    		finish();
    	}
	}
	
	/**
     * Updates the main block of caller info
     * according to call state
	 */
	private void displayMainCallStatus()
	{
		if (Receiver.ccCall == null) return;

        Call.State state = Receiver.ccCall.getState(); 
        switch (state)
        {
            case DIALING:
            case ALERTING:
        		mActionLabel.setVisibility(View.VISIBLE);
        		mActionLabel.setText(getString(R.string.callstate_dialing));
        		mPort.setVisibility(View.INVISIBLE);
        		mElapsedTime.setVisibility(View.GONE);
        		mLoopbackLabel.setVisibility(View.GONE);
                mCallNow.setVisibility(View.GONE);
                mHangup.setVisibility(View.VISIBLE);
                mHangup.setEnabled(true);
                mReject.setVisibility(View.GONE);
                updateSpeakerButton();
                mSpeaker.setVisibility(View.VISIBLE);
                mOptionButtonsGroup.setVisibility(View.INVISIBLE);
                mSlideDialpad.setVisibility(View.GONE);
                if (mDialpad.isOpened()) mDialpad.close();
            	mTamAllreadySeen = false;
            	break;

            case INCOMING:
            case WAITING:
        		mActionLabel.setVisibility(View.VISIBLE);
        		mActionLabel.setText(getString(R.string.callstate_incoming));
        		mPort.setVisibility(View.VISIBLE);
        		mElapsedTime.setVisibility(View.GONE);
        		mLoopbackLabel.setVisibility(View.GONE);
                mCallNow.setVisibility(View.VISIBLE);
                mCallNow.setEnabled(true);
                mHangup.setVisibility(View.GONE);
                mReject.setVisibility(View.VISIBLE);
                mSpeaker.setVisibility(View.GONE);
                mOptionButtonsGroup.setVisibility(View.INVISIBLE);
                mSlideDialpad.setVisibility(View.GONE);
                if (mDialpad.isOpened()) mDialpad.close();
            	mTamAllreadySeen = false;
                break;

        	case ACTIVE:
        		mActionLabel.setVisibility(View.INVISIBLE);
        		mPort.setVisibility(View.INVISIBLE);
        		mElapsedTime.setVisibility(View.VISIBLE);
                mElapsedTime.setBase(Receiver.ccCall.base);
                mElapsedTime.start();
                mLoopbackLabel.setVisibility((RtpStreamReceiver.isLoopbackDisplay()) ?
                		View.VISIBLE : View.GONE);
                mCallNow.setVisibility(View.GONE);
                // make button coming up a bit later because a fast
                // "double click" on mCallNow could act as a single click
                // on mCallNow and a single click on mHangup (does not
                // hang up but confuses SIP stack) 
                mHandler.postDelayed(new Runnable()
                {
					public void run()
					{
						Call call = Receiver.ccCall;
						if ((call != null) && (call.getState() == Call.State.ACTIVE))
						{
							mHangup.setVisibility(View.VISIBLE);
			                mHangup.setEnabled(true);
						}
					}
                	
                }, 300);
                mReject.setVisibility(View.GONE);
                mSpeaker.setVisibility(View.VISIBLE);
                mOptionButtonsGroup.setVisibility(View.VISIBLE);
                updateMuteButton();
                mSlideDialpad.setVisibility(View.VISIBLE);
                if (!mTamAllreadySeen && isInTamCall())
                {
                	mTamAllreadySeen = true;
                	// dialpad and speaker in call with TAM
					if (!mDialpad.isOpened()) mDialpad.animateOpen();
					if (RtpStreamReceiver.speakermode != AudioManager.MODE_NORMAL)
						mSpeaker.performClick();
                }
                updateSpeakerButton(); 
                break;

            case HOLDING:
        		mActionLabel.setVisibility(View.VISIBLE);
        		mActionLabel.setText(getString(R.string.callstate_onhold));
        		mPort.setVisibility(View.INVISIBLE);
        		mElapsedTime.setVisibility(View.GONE);
        		mLoopbackLabel.setVisibility(View.GONE);
                mCallNow.setVisibility(View.GONE);
                mHangup.setVisibility(View.VISIBLE);
                mHangup.setEnabled(true);
                mReject.setVisibility(View.GONE);
                updateSpeakerButton();
                mSpeaker.setVisibility(View.VISIBLE);
                mOptionButtonsGroup.setVisibility(View.VISIBLE);
                updateMuteButton();
                mSlideDialpad.setVisibility(View.GONE);
                if (mDialpad.isOpened()) mDialpad.close();
                break;

        	case DISCONNECTED:
                mElapsedTime.stop();
                if (mElapsedTime.getVisibility() == View.GONE)
                	mActionLabel.setText(getString(R.string.callstate_hangup));
        		mPort.setVisibility(View.INVISIBLE);
        		mLoopbackLabel.setVisibility(View.GONE);
                mCallNow.setVisibility(View.GONE);
                mHangup.setVisibility(View.GONE);
                mReject.setVisibility(View.GONE);
                mSpeaker.setVisibility(View.GONE);
                mOptionButtonsGroup.setVisibility(View.INVISIBLE);
                mSlideDialpad.setVisibility(View.GONE);
                if (mDialpad.isOpened()) mDialpad.close();
            	mTamAllreadySeen = false;
                break;

            case IDLE:
                // The "main CallCard" should never display an idle call!
                FileLog.w(TAG, "displayMainCallStatus: IDLE call!");
        		mActionLabel.setVisibility(View.INVISIBLE);
        		mPort.setVisibility(View.INVISIBLE);
        		mElapsedTime.setVisibility(View.GONE);
        		mLoopbackLabel.setVisibility(View.GONE);
                mCallNow.setVisibility(View.GONE);
                mHangup.setVisibility(View.GONE);
                mReject.setVisibility(View.GONE);
                mSpeaker.setVisibility(View.GONE);
                mOptionButtonsGroup.setVisibility(View.INVISIBLE);
                mSlideDialpad.setVisibility(View.GONE);
                if (mDialpad.isOpened()) mDialpad.close();
            	mTamAllreadySeen = false;
                break;

            default:
                FileLog.w(TAG, "displayMainCallStatus: unexpected call state: " + state);
                break;
        }

        // Update onscreen info for a regular call (which presumably
        // has only one connection.)
        Connection conn = Receiver.ccCall.getEarliestConnection();

        boolean isPrivateNumber = false; // TODO: need isPrivate() API

        if (conn == null)
        {
        	FileLog.d(TAG, "displayMainCallStatus: connection is null, using default values.");
            // if the connection is null, we run through the behaviour
            // we had in the past, which breaks down into trivial steps
            // with the current implementation of getCallerInfo and
            // updateDisplayForPerson.
            updateDisplayForPerson(null, isPrivateNumber, false, Receiver.ccCall);
        }
        else
        {
            FileLog.d(TAG, "  - CONN: " + conn + ", state = " + conn.getState());

            // make sure that we only make a new query when the current
            // callerinfo differs from what we've been requested to display.
            boolean runQuery = true;
            Object o = conn.getUserData();
            if (o instanceof PhoneUtils.CallerInfoToken)
                runQuery = mPhotoTracker.isDifferentImageRequest(
                        ((PhoneUtils.CallerInfoToken) o).currentInfo);
            else
                runQuery = mPhotoTracker.isDifferentImageRequest(conn);

            if (runQuery)
            {
            	FileLog.d(TAG, "- displayMainCallStatus: starting CallerInfo query...");
                PhoneUtils.CallerInfoToken info =
                        PhoneUtils.startGetCallerInfo(this, conn, this, Receiver.ccCall);
                updateDisplayForPerson(info.currentInfo, isPrivateNumber, !info.isFinal, Receiver.ccCall);
            }
            else
            {
                // No need to fire off a new query.  We do still need
                // to update the display, though (since we might have
                // previously been in the "conference call" state.)
            	FileLog.d(TAG, "- displayMainCallStatus: using data we already have...");
                if (o instanceof CallerInfo)
                {
                    CallerInfo ci = (CallerInfo) o;
                    FileLog.d(TAG, "   ==> Got CallerInfo; updating display: ci = " + ci);
                    updateDisplayForPerson(ci, false, false, Receiver.ccCall);
                }
                else if (o instanceof PhoneUtils.CallerInfoToken)
                {
                    CallerInfo ci = ((PhoneUtils.CallerInfoToken) o).currentInfo;
                    FileLog.d(TAG, "   ==> Got CallerInfoToken; updating display: ci = " + ci);
                    updateDisplayForPerson(ci, false, true, Receiver.ccCall);
                }
                else
                {
                    FileLog.w(TAG, "displayMainCallStatus: runQuery was false, "
                          + "but we didn't have a cached CallerInfo object!  o = " + o);
                    // TODO: any easy way to recover here (given that
                    // the CallCard is probably displaying stale info
                    // right now?)  Maybe force the CallCard into the
                    // "Unknown" state?
                }
            }
        }

        // In some states we override the "photo" ImageView to be an
        // indication of the current state, rather than displaying the
        // regular photo as set above.
        updateActionImageForCallState(Receiver.ccCall);
	}

	private void updateSpeakerButton()
	{
		mSpeaker.setImageResource((RtpStreamReceiver.speakermode == AudioManager.MODE_NORMAL) ?
				R.drawable.btn_speakeroff : R.drawable.btn_speaker);
	}
	
	private void updateMuteButton()
	{
		mMute.setImageResource((Receiver.isMuted()) ?
				R.drawable.btn_incall_micon : R.drawable.btn_incall_micoff);
	}
	
	/**
	 * Sets ActionImage according to call state
	 * currently we override the photo in every state!!
	 * 
	 * @param call
	 */
	private void updateActionImageForCallState(Call call)
	{
        Call.State state = call.getState();
        switch (state)
        {
	        case IDLE: break;
	    	
	        case ACTIVE:
	        	mActionImage.setImageResource(R.drawable.callstate_active);
	        	mActionImageHDOverlay.setVisibility(
	        			(RtpStreamReceiver.getCodecTypeDisplay() ==
	        			 RtpStreamReceiver.CodecType.CODECTYPE_HD) ?
	        					View.VISIBLE : View.GONE);
                break;

	        case HOLDING:
	        	mActionImage.setImageResource(R.drawable.callstate_active);
	        	mActionImageHDOverlay.setVisibility(View.GONE);
                break;

	        case INCOMING:
	        case WAITING:
	        	mActionImage.setImageResource(R.drawable.callstate_incoming);
	        	mActionImageHDOverlay.setVisibility(View.GONE);
                break;

	        case DISCONNECTED:
	        	mActionImage.setImageResource(R.drawable.callstate_hangup);
	        	mActionImageHDOverlay.setVisibility(View.GONE);
                break;

            case DIALING:
            case ALERTING:
            	mActionImage.setImageResource(R.drawable.callstate_dialing);
	        	mActionImageHDOverlay.setVisibility(View.GONE);
                break;

            default:
                FileLog.w(TAG, "updatePhotoForCallState: unexpected call state: " + state);
                mActionImage.setVisibility(View.GONE);
	        	mActionImageHDOverlay.setVisibility(View.GONE);
                return;
        }
    	mActionImage.setVisibility(View.VISIBLE);
	}

	/**
     * Updates the name and number label fields
     * according to call state
     *
     * If the current call is a conference call, use
     * updateDisplayForConference() instead.
     * 
	 * @param info
	 * @param isPrivateNumber
	 * @param isTemporary
	 * @param call
	 */
	private void updateDisplayForPerson(CallerInfo info,
            boolean isPrivateNumber, boolean isTemporary, Call call)
	{
        String name = null;
        String displayNumber = null;
        String label = null;

        if (info != null)
        {
        	String phoneNumber = PhoneNumberHelper.stripNumber(info.phoneNumber);
            if (TextUtils.isEmpty(info.name))
            {
            	name = Receiver.engine(this).getIncomingCallersName();
            	if (TextUtils.isEmpty(name) && !TextUtils.isEmpty(phoneNumber))
            		name = phoneNumber;
            }
            else
            {
                name = info.name;
                displayNumber = phoneNumber;
                label = info.phoneLabel;
            }
        }
        else name = Receiver.engine(this).getIncomingCallersName();
        if (TextUtils.isEmpty(name)) name = getString(R.string.unknown);
        mName.setText(name);
        mName.setVisibility(View.VISIBLE);
        
        if (displayNumber != null)
        {
            if (label != null)
            	displayNumber = label + " " + displayNumber;
            mNumber.setText(displayNumber);
            mNumber.setVisibility(View.VISIBLE);
        }
        else  mNumber.setVisibility(View.GONE);

        // display called name
        displayNumber = Receiver.engine(this).getIncomingCalleesName();
    	mPort.setText((displayNumber == null) ? "" : displayNumber);
	}
	
	/**
     * Updates the "Ongoing call" box in the "other call" info area
     * (ie. the stuff in the otherCallOngoingInfo block)
     * based on the specified Call.
     * Or, clear out the "ongoing call" box if the specified call
     * is null or idle.
	 * 
	 * @param phone
	 * @param call
	 */
	private void displayOngoingCallStatus(Phone phone, Call call)
    {
		// TODO implement hold and incoming on busy
	}

    /**
     * Updates the "on hold" box in the "other call" info area
     * (ie. the stuff in the otherCallOnHoldInfo block)
     * based on the specified Call.
     * Or, clear out the "on hold" box if the specified call
     * is null or idle.
     * 
     * @param phone
     * @param call
     */
    private void displayOnHoldCallStatus(Phone phone, Call call)
    {
		// TODO implement hold and incoming on busy
	}

	/* (non-Javadoc)
	 * @see de.avm.android.fritzapp.sipua.phone.CallerInfoAsyncQuery.OnQueryCompleteListener#onQueryComplete(int, java.lang.Object, de.avm.android.fritzapp.sipua.phone.CallerInfo)
	 */
	public void onQueryComplete(int token, Object cookie, CallerInfo ci)
	{
        if (cookie instanceof Call)
        {
            // grab the call object and update the display for an individual call,
            // as well as the successive call to update image via call state.
            // If the object is a textview instead, we update it as we need to.
            FileLog.d(TAG, "callerinfo query complete, updating ui from displayMainCallStatus()");
            Call call = (Call) cookie;
            updateDisplayForPerson(ci, false, false, call);
            updateActionImageForCallState(call);

        }
        else if (cookie instanceof TextView)
        {
            FileLog.d(TAG, "callerinfo query complete, updating ui from ongoing or onhold");
            ((TextView)cookie).setText(PhoneUtils.getCompactNameFromCallerInfo(ci, (Context)this));
        }
	}
	
	private boolean isInTamCall()
	{
		if (Receiver.call_state == UserAgent.UA_STATE_INCALL)
		{
			BoxInfo boxInfo = ComSettingsChecker.getBoxInfo();
	        Connection conn = Receiver.ccCall.getEarliestConnection();
			if ((boxInfo != null) && (conn != null)) 
			{
	        	String phoneNumber = PhoneNumberHelper.stripNumber(conn
	        			.getAddress());
	        	if (!TextUtils.isEmpty(phoneNumber) &&
	        			phoneNumber.equals(TamPreference.getCachedSelectedTam()))
					return true;
			}
		}
		
		return false;
	}

    private class HeadsetReceiver extends BroadcastReceiver
    {
    	public void register()
    	{
    		// catch call button and plugging of wired headset
    		// higher priority needed to catch it before android's media app
        	IntentFilter intentFilter = new IntentFilter();
        	intentFilter.addAction(Intent.ACTION_MEDIA_BUTTON);
        	intentFilter.addAction(Intent.ACTION_HEADSET_PLUG);
        	intentFilter.setPriority(2);
            registerReceiver(this, intentFilter); 
    	}
    	
    	public void unregister()
    	{
    		unregisterReceiver(this);
    	}
    	
		public void onReceive(Context context, Intent intent)
		{
			String intentAction = intent.getAction();
		    if (intentAction.equals(Intent.ACTION_HEADSET_PLUG))
		    {
		    	onPlug();
		    }
		    else if (intentAction.equals(Intent.ACTION_MEDIA_BUTTON))
		    {
		    	onButton((KeyEvent)intent.getParcelableExtra(
		    			Intent.EXTRA_KEY_EVENT));
		    }
		}
    	private void onPlug()
    	{
	        Call call = Receiver.ccCall;
	        if (call != null)
	        {
		    	switch (Receiver.ccCall.getState())
		    	{
		            case DIALING:
		            case ALERTING:
		        	case ACTIVE:
		            case HOLDING:
				    	mHandler.sendEmptyMessage(MSG_HEADSETPLUG);
		            	break;
		    	}
	        }
    	}
    	
    	private void onButton(KeyEvent keyEvent)
    	{
	        if ((keyEvent == null) ||
	        		(keyEvent.getKeyCode() != KeyEvent.KEYCODE_HEADSETHOOK) ||
	        		(keyEvent.getAction() != KeyEvent.ACTION_DOWN))
	        	return;
	        
	        if (Receiver.headset <= 0)
	        	return; // no headset connected
			
	        if (Receiver.call_state == UserAgent.UA_STATE_INCOMING_CALL)
	        {
	    		if (keyEvent.getRepeatCount() == 0)
					mHandler.sendEmptyMessageDelayed(MSG_ANSWER, 300); // answer incoming call
	            abortBroadcast();
	        }
	        else if (Receiver.call_state == UserAgent.UA_STATE_INCALL)
	        {
	    		if (keyEvent.getRepeatCount() == 0)
					mHandler.sendEmptyMessageDelayed(MSG_HANGUP, 300); // end call
	            abortBroadcast();
	        }
	        // in all other states it has to be handled by default (e.g. android's media app)
    	}
    };
}
