/* 
 * Copyright 2015 by AVM GmbH <info@avm.de>
 *
 * This software contains free software; you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License ("License") as 
 * published by the Free Software Foundation  (version 3 of the License). 
 * This software 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 copy of the 
 * License you received along with this software for more details.
 */

package de.avm.android.fritzapp.service;

import java.lang.ref.WeakReference;
import java.security.cert.X509Certificate;
import java.util.LinkedList;
import java.util.Timer;
import java.util.TimerTask;

import de.avm.android.fritzapp.R;
import de.avm.android.fritzapp.com.ComSettingsChecker;
import de.avm.android.fritzapp.com.ConnectionProblem;
import de.avm.android.fritzapp.com.VoIPAutoConfigurator;
import de.avm.android.fritzapp.com.discovery.BoxFinder;
import de.avm.android.fritzapp.com.discovery.BoxInfo;
import de.avm.android.fritzapp.gui.Dialpad;
import de.avm.android.fritzapp.gui.Help;
import de.avm.android.fritzapp.gui.SettingsActivity;
import de.avm.android.fritzapp.gui.SettingsFritzBoxActivity;
import de.avm.android.fritzapp.gui.SettingsVoIPConfigActivity;
import de.avm.android.fritzapp.gui.TamPreference;
import de.avm.android.fritzapp.sipua.ui.Receiver;
import de.avm.android.fritzapp.sipua.ui.Sipdroid;
import de.avm.android.fritzapp.util.CallRouteExceptions;
import de.avm.android.fritzapp.util.InetAddressHelper;
import de.avm.android.fritzapp.util.PhoneNumberHelper;
import de.avm.android.tr064.Tr064Capabilities;
import de.avm.android.tr064.exceptions.SoapException;
import de.avm.android.tr064.exceptions.SslCertificateException;
import de.avm.android.tr064.net.Fingerprint;
import de.avm.fundamentals.logger.FileLog;

import android.app.Service;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences.Editor;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.widget.Toast;

public class BoxService extends Service
{
	private static final String TAG = "BoxService";

	public enum Command
	{
		STARTSTOP,
		SETSIP, RECONNECT,
		SIP_KEEPALIVE, SIP_EXPIRED,
		CALL
	}

	public static final String EXTRA_COMMAND =
			"de.avm.android.fritzapp.service.COMMAND";
	public static final String EXTRA_SIPSTATE =
			"de.avm.android.fritzapp.service.SIPSTATE";
	public static final String EXTRA_SIPERROR =
			"de.avm.android.fritzapp.service.SIPERROR";
	public static final String EXTRA_NUMBER =
			"de.avm.android.fritzapp.service.NUMBER";
	public static final String EXTRA_FRITZ_APP =
			"de.avm.android.fritzapp.service.FRITZ_APP";
	
	private static final String PREF_FIRSTRUN = "firstrun";
	
	private static final int CHECK_CONNECTION_RETRY = 3;			// retry count
	private static final long CHECK_CONNECTION_INTERVAL_BASE = 30;	// retry interval base in s
	
	private Integer mStartId = null;
	private Handler mHandler = new Handler();
	private final Binder mBinder = new Binder();
	private LinkedList<WeakReference<IBoxServiceListener>> mBinderListeners = new LinkedList<>();
	private OnSearchDoneListener mBoxSearchDoneListener =
			new OnSearchDoneListener();
	private ComErrorMessage mLastComErrorMessage = null;
	
	private ComStatus mComStatus = null;
	private ComLock mComLock = null;
	private CheckConnectionTask mCheckConnectionTask = null;
	private int mCheckConnectionRetryCount = 0;
	private Timer mCheckConnectionRetryTimer = null;
	private boolean mFirstRun = false;
	private boolean mFirstConn = true;
	private boolean mAskedForPassword = false;

	private Receiver mReceiver = null;
	private boolean mOverrideClirOnce = false;
	
	@Override
	public void onCreate() 
	{
		FileLog.d(TAG, "onCreate()");

		ComSettingsChecker.initialize(this);
	}

	@SuppressWarnings("deprecation")
    @Override
	public void onStart(Intent intent, int startId) 
	{
		onCommand(intent, startId);
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId)
	{
		onCommand(intent, startId);
	    return START_STICKY;
	}
	
	public void onCommand(Intent intent, int startId) 
	{
		boolean isActive = (mStartId != null);
		Command command = Command.STARTSTOP;
		try
		{
			command = Command.values()[intent.getIntExtra(EXTRA_COMMAND,
					Command.STARTSTOP.ordinal())];
		}
		catch(Exception e)
		{
			// keep default STARTSTOP");
		}

		switch (command)
		{
			case STARTSTOP:
				if (Sipdroid.on(getApplicationContext()))
				{
					mStartId = startId;
					if (!isActive) startUp();
					return;
				}
				else if (isActive)
				{
					// service not needed anymore, shut it down
					mStartId = startId;
					shutDown();
					return;
				}
				break;
				
			case SETSIP:
				if (isActive)
				{
					mStartId = startId;
					int newState = intent.getIntExtra(EXTRA_SIPSTATE,
							ComSettingsChecker.SIP_NOTREGISTERED);
					String newError = intent.getStringExtra(EXTRA_SIPERROR); 
					
					// ignore error state if unregistered (e.g. timeout from calling
					// SipdroidEngine.unregister() with box not reachable any more)
					if ((newState != ComSettingsChecker.SIP_AWAY) ||
							(ComSettingsChecker.getSipState() !=
									ComSettingsChecker.SIP_NOTREGISTERED))
					{
                        // lost SIP?
                        boolean lost = (newState == ComSettingsChecker.SIP_AWAY) &&
                                (ComSettingsChecker.getSipState() ==
                                        ComSettingsChecker.SIP_AVAILABLE);

                        if (ComSettingsChecker.setSipState(newState, newError))
                            refreshComStatus();

                        // lost SIP? check if box has been lost
                        if (lost) onPing();
					}
				}
				break;

			case RECONNECT:
				if (isActive)
				{
					mStartId = startId;
					onReconnect(null);
				}
				break;
				
			case SIP_KEEPALIVE:
				if (isActive)
				{
					mStartId = startId;
					new Thread(new Runnable()
					{
						public void run()
						{
					    	Receiver.engine(BoxService.this).keepAlive();
						}
					}).start();
				}
				break;
				
			case SIP_EXPIRED:
				if (isActive)
				{
					mStartId = startId;
                    new Thread(new Runnable()
                    {
                        public void run()
                        {
                            Receiver.engine(BoxService.this).expire();
                        }
                    }).start();
				}
				break;

			case CALL:
				if (isActive) mStartId = startId;
				String number = intent.getStringExtra(EXTRA_NUMBER);
				if (!TextUtils.isEmpty(number))
				{
					Sipdroid.mBackToMainActivity = intent.getBooleanExtra(
							EXTRA_FRITZ_APP, false);
					onCall(number);
				}
				break;

			default: 
				if (isActive) mStartId = startId;
				break;
		}

		if (!isActive) stopSelfResult(startId); // service not needed
	}
	
	@Override
	public IBinder onBind(Intent intent)
	{
		return mBinder;
	}
	
	@Override
	public boolean onUnbind (Intent intent)
	{
		mBinderListeners.clear();
		return false;
	}
	
	@Override
	public void onDestroy() 
	{
		FileLog.d(TAG, "onDestroy()");

		ComSettingsChecker.clearBoxInfo();
		if (mComLock != null) mComLock.clear();
		if (mComStatus != null) mComStatus.clear();
        if (mReceiver != null) unregisterReceiver(mReceiver);
	}
	
	private void startUp()
	{
		FileLog.d(TAG, "startUp()");

		// manage settings
		mFirstRun = PreferenceManager.getDefaultSharedPreferences(this)
				.getBoolean(PREF_FIRSTRUN, true);
		if (mFirstRun)
		{
			Editor edit = PreferenceManager.getDefaultSharedPreferences(this)
					.edit();
			edit.putBoolean(PREF_FIRSTRUN, false);
			edit.apply();
		}
		SettingsActivity.prepareSettings(this, mFirstRun);
		
		mLastComErrorMessage = null;
		
		mComStatus = new ComStatus(this);
		mComLock = new ComLock(this);
		
		mReceiver = new Receiver();
		IntentFilter filter = new IntentFilter();
		filter.addAction(Receiver.ACTION_DATA_STATE_CHANGED);
		filter.addAction(Receiver.ACTION_PHONE_STATE_CHANGED);
		filter.addAction(Receiver.ACTION_DOCK_EVENT);
		filter.addAction(Intent.ACTION_HEADSET_PLUG);
        registerReceiver(mReceiver, filter);

        NetworkChangedHandler.getInstance(this)
                .registerCallback(new NetworkChangedHandler.OnNetworkChangedListener()
                {
                    public void onChanged(final boolean forceReconnect)
                    {
                        if (mStartId != null)
                        {
                            mHandler.postDelayed(new Runnable()
                            {
                                public void run()
                                {
                                    if (forceReconnect || !ComSettingsChecker.isConnected())
                                        onReconnect(null); // disconnect/try to connect again
                                    else
                                        onPing(); // check if still connected
                                }
                            }, 1000);
                        }
                    }
                });

        startCheckConnection(true);
	}
	
	private void shutDown()
	{
		FileLog.d(TAG, "shutdown()");

        NetworkChangedHandler.getInstance(this).unregisterCallback();

		new Thread(new Runnable()
		{
			public void run()
			{
				if (mStartId != null)
				{
					cancelCheckConnectionRetry();
					mLastComErrorMessage = null;

					Receiver.reRegister(0);
					Receiver.engine(BoxService.this).unregister();
					try
					{
						Thread.sleep(2000);
					}
					catch (InterruptedException ignored)
					{
					}
					Receiver.engine(BoxService.this).halt();
					Receiver.mSipdroidEngine = null;
					// because we unregistered SIP we have to stop now
					// (independently from mStartId) 
					stopSelf();
					mStartId = null;
				}
			}
		}).start();
	}
	
	private void onReconnect(String switchToUdn)
	{
		cancelCheckConnectionRetry();
		mLastComErrorMessage = null;
		disconnectSip();
		ComSettingsChecker.clearBoxInfo();
		if (!TextUtils.isEmpty(switchToUdn))
			ComSettingsChecker.switchToBox(switchToUdn);
		updateTamInput();
		refreshComStatus();
		startCheckConnection(true);
	}

    private void onPing()
    {
        new Thread(new Runnable()
        {
            public void run()
            {
                FileLog.d(TAG, "onPing()");
                if (!ComSettingsChecker.testCurrentConnection())
                {
                    mHandler.post(new Runnable()
                    {
                        public void run()
                        {
                            FileLog.d(TAG, "onPing: current connection lost");
                            onReconnect(null);
                        }
                    });
                }
            }
        }).start();
    }

	private void onCall(String number)
	{
		if (CallRouteExceptions.isException(this, number) &&
				!PhoneNumberHelper.isInternalNumber(number))
		{
			MobileFallbackActivity.sendMobileFallbackIntent(this, number, false);
			return;
		}
		else if ((mStartId != null) &&
				(ComSettingsChecker.getSipState() == ComSettingsChecker.SIP_AVAILABLE))
		{
			String decoratedNumber = PhoneNumberHelper.decorateNumber(this,
					mOverrideClirOnce,
					PhoneNumberHelper.fixInternationalDialingPrefix(
							PhoneNumberHelper.stripSeparators(number)));
			boolean callAnonymous = PhoneNumberHelper.isClirInSip(this, mOverrideClirOnce);
			mOverrideClirOnce = false;
			
			if (Receiver.engine(this).call(decoratedNumber, callAnonymous))
			{
				Dialpad.saveAsRedial(this, number);
				return;
			}
		}

		MobileFallbackActivity.sendMobileFallbackIntent(this, number, true);
	}

	private void refreshComStatus()
	{
		if (mComLock != null) mComLock.refresh();
		
		if ((mComStatus != null) && mComStatus.refresh())
		{
			for (WeakReference<IBoxServiceListener> entry : mBinderListeners)
			{
				try
				{
					IBoxServiceListener listener = entry.get(); 
					if (listener != null) listener.onComStatusChanged();
				}
				catch(Exception e)
				{
                    FileLog.w(TAG, e.getMessage(), e);
				}
			}
		}
	}
	
	private void updateTamInput()
	{
		mHandler.post(new Runnable()
		{
			public void run()
			{
				// refresh it, so its cached value is available 
				TamPreference.getSelectedTam(BoxService.this,
						new TamPreference.OnResultListener<String>()
				{
					public void onResult(String udn, String result)
					{
						mHandler.post(new Runnable()
						{
							public void run()
							{
								// to get activities updated on changed TAM info
								refreshComStatus();
							}
						});
					}
		
					public void onError(Exception error)
					{
					}
				});
			}
		});
	}
	
	private void connectSip(final boolean restartEngine)
	{
//		Log.d(TAG, String.format("connectSip(%s)",
//				Boolean.toString(restartEngine)));

		VoIPAutoConfigurator task = VoIPAutoConfigurator.start(this,
				new VoIPAutoConfigurator.OnCompleteListener()
		{
			public void onComplete(VoIPAutoConfigurator.Result result, String errorMessage)
			{
//				Log.d(TAG, "VoIPAutoConfigurator.OnCompleteListener.onComplete(" + result +
//						")" + errorMessage);
				if (restartEngine || !Receiver.engine(BoxService.this).isRegistered())
				{
					Receiver.engine(BoxService.this).updateDNS();
					Receiver.engine(BoxService.this).halt();
			    	Receiver.engine(BoxService.this).StartEngine();
				}
				else Receiver.engine(BoxService.this).register();
				mOverrideClirOnce = false;

				switch (result)
				{
					case MANUAL:
//						Log.d(TAG, "onComplete: start SettingsVoIPConfigActivity");
						// no user configured automatically -> do it manually
						startActivity(new Intent(BoxService.this,
								SettingsVoIPConfigActivity.class)
								.putExtra(SettingsVoIPConfigActivity.EXTRA_AUTOFINISH, true)
								.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
						break;
						
					case ERROR:
						if (!TextUtils.isEmpty(errorMessage))
						{
							// show error
//							Log.d(TAG, "onComplete: show error");
							onComError(new ComErrorMessage(errorMessage,
									android.R.drawable.ic_dialog_alert));
						}
						break;
				}
			}

			@Override
			public void onProgress()
			{
				updateTamInput();
			}
		});

		if (task == null)
		{
//			Log.d(TAG, "  no VoIP client auto configuration");
			// no auto configuration
			if (restartEngine || !Receiver.engine(BoxService.this).isRegistered())
			{
				Receiver.engine(this).updateDNS();
				Receiver.engine(this).halt();
		    	Receiver.engine(this).StartEngine();
			}
			else Receiver.engine(this).register();
			mOverrideClirOnce = false;
			updateTamInput();
		}
	}
	
	private void disconnectSip()
	{
		try
		{
			Receiver.reRegister(0);
			Receiver.engine(this).unregister();
			return;
		}
		catch(Exception e)
		{
			// don't know why it cashes (market crash reports only)
			// try to work around...
		}
		try { Thread.sleep(100); }
		catch (InterruptedException ignored) {}
		try
		{
			Receiver.reRegister(0);
			Receiver.engine(this).unregister();
		}
		catch(Exception e)
		{
			// 2nd attempt to disconnect SIP, ignore
		}
	}
	
	public class Binder extends android.os.Binder implements IBoxService
	{
		public void registerListener(IBoxServiceListener listener)
		{
			if (listener != null)
			{
				for (WeakReference<IBoxServiceListener> entry : mBinderListeners)
					if (entry.get() == listener) return;
				mBinderListeners.add(new WeakReference<IBoxServiceListener>(listener));
			}
		}
		
		public void unregisterListener(IBoxServiceListener listener)
		{
			if (listener != null)
			{
				for (WeakReference<IBoxServiceListener> entry : mBinderListeners)
					if (entry.get() == listener)
					{
						mBinderListeners.remove(entry);
						break;
					}
			}
		}
		
		public boolean isActive()
		{
			return mStartId != null;
		}
		
		public void reconnect()
		{
			if (isActive()) onReconnect(null);
		}

		public void reconnectSip()
		{
			if (isActive())
			{
				Receiver.engine(BoxService.this).halt();
				Receiver.engine(BoxService.this).StartEngine();
			}
		}

		public void searchBoxes(String[] additionalHosts)
		{
			try
			{
				ComSettingsChecker.getBoxFinder().startSearch(
						mBoxSearchDoneListener, additionalHosts);
			}
			catch(Exception e)
			{
                FileLog.w(TAG, e.getMessage(), e);
				mBoxSearchDoneListener.onSearchDone(e);
			}
		}

		public void switchTo(String udn)
		{
			if (isActive()) onReconnect(udn);
		}

		public ComErrorMessage getLastComError()
		{
			if (isActive()) return mLastComErrorMessage;
			return null;
		}

		public void removeLastComError(ComErrorMessage comError)
		{
			if ((comError != null) && (mLastComErrorMessage != null) &&
					(comError.getTimeStamp() >= mLastComErrorMessage.getTimeStamp()))
				mLastComErrorMessage = null;
		}

		public boolean isOverrideClirOnce()
		{
			return mOverrideClirOnce;
		}

		public void setOverrideClirOnce(boolean on)
		{
			mOverrideClirOnce = on;
			if (mOverrideClirOnce)
				Toast.makeText(BoxService.this,
						(PhoneNumberHelper.isClir(BoxService.this)) ?
						R.string.hint_clir_off_once : R.string.hint_clir_on_once,
						Toast.LENGTH_SHORT).show();
		}

        public void onNotificationOptionChanged()
        {
            if (mComStatus != null)
                mComStatus.refreshDisplay();
        }
	}
	
	private class OnSearchDoneListener implements BoxFinder.OnSearchDoneListener
	{
		public void onSearchDone()
		{
			onSearchDone(null);
		}
		
		public void onSearchDone(final Exception error)
		{
			mHandler.post(new Runnable()
			{
				public void run()
				{
					for (WeakReference<IBoxServiceListener> entry : mBinderListeners)
					{
						try
						{
							IBoxServiceListener listener = entry.get(); 
							if (listener != null) listener.onBoxesSearchDone(error);
						}
						catch(Exception e)
						{
                            FileLog.w(TAG, e.getMessage(), e);
						}
					}
				}
			});
		}
	}

	/**
	 * Background Task to check if the connection to the FRITZ!Box can be established.
	 * 
	 */
	private class CheckConnectionTask extends
			AsyncTask<Boolean, Integer, ConnectionProblem>
	{
		private boolean mNewSearch;
		private String mLastHost = "";
		private String mLastUdn = "";

		protected void onPreExecute()
		{
			super.onPreExecute();
			BoxInfo boxInfo = ComSettingsChecker.getBoxInfo();
			if (boxInfo != null)
			{
				mLastHost = boxInfo.getLocation().getHost();
				mLastUdn = boxInfo.getUdn();
			}
 			FileLog.d(TAG, String.format("CheckConnectionTask.onPreExecute() url=\"%s\"",
 					mLastHost));
		}

		protected ConnectionProblem doInBackground(Boolean... showMessageParms)
		{
            mNewSearch = (showMessageParms.length > 0) ? showMessageParms[0] : true;
            return ComSettingsChecker.checkStartUpConnection(getBaseContext(), mNewSearch);
		}

        protected void onPostExecute(ConnectionProblem problem)
		{
			super.onPostExecute(problem);
			FileLog.d(TAG, String.format("CheckConnectionTask.onPostExecute() - %s",
					problem.toString()));

			mBoxSearchDoneListener.onSearchDone();
			synchronized(BoxService.this)
			{
				if (mCheckConnectionTask == this) mCheckConnectionTask = null;
			}

			boolean firstRun = mFirstRun;
			mFirstRun = false;
			boolean askedForPassword = false;
			boolean retry = false;
			onComError(null); // remove old error
			
			if (problem == ConnectionProblem.OK)
			{
				refreshComStatus();
				String locationHost = ComSettingsChecker.getLocationHost();
				if (!mLastHost.equals(locationHost) ||
						!mLastUdn.equals(ComSettingsChecker.getUdn()))
				{
					// new connection or url has been changed while checking
					mFirstConn = true; 
//					Log.d(TAG, String.format("  url or UDN has been changed (\"%s\").",
//							locationHost));
			    	connectSip(true);
				}
				else connectSip(false);
				
				onFirstBox();
				
				// check for IPv4 non-site-local addresses
				try
				{
					if (!TextUtils.isEmpty(locationHost) &&
							!InetAddressHelper.isSiteLocalAddress(locationHost))
					{
						onComError(new ComErrorMessage(BoxService.this.getString(
								R.string.fritzbox_no_local_site)));
					}
				}
				catch(Exception e) { /*ignore*/ }
			}
			else if (problem == ConnectionProblem.FRITZBOX_PASSWORD)
			{
				// password needed to use TR-064
				BoxInfo boxInfo = ComSettingsChecker.getBoxInfo();
				if (boxInfo != null)
				{
					// warning?
					String warning = null;
					@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
                    Exception cause = problem.getCause();
					if ((cause != null) && SoapException.class
							.isAssignableFrom(cause.getClass()))
					{
						warning = getString(R.string.password_box_accessdenied);
						askedForPassword = false;
					}
					else if (mAskedForPassword)
						warning = getString(R.string.password_box_warning);
					else
						askedForPassword = true;
					onComError(new ComErrorMessage(BoxService.this, problem,
							boxInfo.getUdn(), warning,
							ComSettingsChecker.hasAnonymousLogin()));
				}
			}
			else if ((problem == ConnectionProblem.FRITZBOX_MISSING) &&
					(ComSettingsChecker.getBoxes().hasAvailables()) &&
					!ComSettingsChecker.getBoxes().hasPreferences())
			{
				// have never been connected before and more than one
				// available -> ask user
				startActivity(new Intent(BoxService.this, SettingsFritzBoxActivity.class)
					.putExtra(SettingsFritzBoxActivity.EXTRA_AUTOFINISH, true)
					.putExtra(SettingsFritzBoxActivity.EXTRA_SEARCH, false)
					.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
			}
			else if ((problem == ConnectionProblem.FRITZBOX_NOTR064) ||
					(problem == ConnectionProblem.SSL_ERROR))
			{
				onComError(new ComErrorMessage(BoxService.this, problem));
				// repeat connection set up if ssl error
				retry = (problem == ConnectionProblem.SSL_ERROR);
			}
			else if (problem == ConnectionProblem.CERTIFICATE_ERROR)
			{
				X509Certificate cert = SslCertificateException
						.getCertificate(problem.getCause());
				BoxInfo boxInfo = ComSettingsChecker.getBoxInfo();
				if (cert != null)
				{
					try
					{
						FileLog.d(TAG, "Certificate has been changed, ask user for consent. SHA-1: \"" +
								new Fingerprint(cert, Fingerprint.Type.SHA1).toUserfriendlyString() + "\"");
					}
					catch (Exception ignored) { }
					onComError(new ComErrorMessage(BoxService.this, problem,
							boxInfo.getUdn(), cert));
				}
				else onComError(new ComErrorMessage(BoxService.this, problem));
			}
			else if (firstRun)
			{
				Help.showHelp(BoxService.this, Help.HelpTopic.FINDBOX);
			}
			else if (problem.isError())
			{
				onComError(new ComErrorMessage(BoxService.this, problem));
			}
			else if (problem == ConnectionProblem.FRITZBOX_MISSING)
			{
				retry = true;
			}
			mAskedForPassword = askedForPassword;
			refreshComStatus();
			
			if (retry)
			{
				if (Sipdroid.on(BoxService.this.getApplicationContext()))
					startCheckConnectionRetry(mNewSearch);
			}
			else cancelCheckConnectionRetry();
		}
	}

	private void startCheckConnection(final boolean newSearch)
	{
		mHandler.post(new Runnable()
		{
			public void run()
			{
				synchronized(BoxService.this)
				{
					if (mCheckConnectionTask == null)
					{
						if (mCheckConnectionRetryTimer != null)
						{
							// cancel pending retries timer
							mCheckConnectionRetryTimer.cancel();
							mCheckConnectionRetryTimer = null;
						}
						mCheckConnectionTask = new CheckConnectionTask();
						mCheckConnectionTask.execute(newSearch);
					}
				}
			}
		});
	}
	
	private synchronized void startCheckConnectionRetry(final boolean newSearch)
	{
		mCheckConnectionRetryCount++;
		if (mCheckConnectionRetryCount <= CHECK_CONNECTION_RETRY)
		{
			final Timer timer = new Timer();
			mCheckConnectionRetryTimer = timer;
			try
			{
				mCheckConnectionRetryTimer.schedule(new TimerTask()
				{
					public void run()
					{
						boolean retry;
						synchronized(BoxService.this)
						{
							retry = (timer == mCheckConnectionRetryTimer);
							mCheckConnectionRetryTimer = null;
						}
						if (retry)
							startCheckConnection(newSearch);
					}
				}, CHECK_CONNECTION_INTERVAL_BASE *
						(long)mCheckConnectionRetryCount * 1000L);
			}
			catch(Exception e)
			{
                FileLog.w(TAG, e.getMessage(), e);
				cancelCheckConnectionRetry();
			}
		}
		else cancelCheckConnectionRetry();
	}

	private synchronized void cancelCheckConnectionRetry()
	{
		mCheckConnectionRetryCount = 0;
		if (mCheckConnectionRetryTimer != null)
		{
			mCheckConnectionRetryTimer.cancel();
			mCheckConnectionRetryTimer = null;
		}
	}
	
	private void onFirstBox()
	{
		// first connect to this box in this session -> VoIP client configuration
		if (mFirstConn)
		{
			mFirstConn = false;
			if (Sipdroid.getSipUser().length() == 0)
			{
				// no account -> VoIP client configuration (only for boxes which could and have
				// to be configured)
				Tr064Capabilities capabilities = ComSettingsChecker.getTr064Capabilities();
				if ((capabilities != null) &&
						capabilities.has(Tr064Capabilities.Capability.VOIP_CONF) &&
						!capabilities.has(Tr064Capabilities.Capability.VOIP_CONF_ID))
				{
					startActivity(new Intent(this, SettingsVoIPConfigActivity.class)
							.putExtra(SettingsVoIPConfigActivity.EXTRA_AUTOFINISH, true)
							.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
				}
			}
		}
	}
	
	private void onComError(final ComErrorMessage comErrorMessage)
	{
		mLastComErrorMessage = comErrorMessage;
		if (comErrorMessage != null)
		{
			mHandler.post(new Runnable()
			{
				public void run()
				{
					for (WeakReference<IBoxServiceListener> entry : mBinderListeners)
					{
						try
						{
							IBoxServiceListener listener = entry.get(); 
							if (listener != null) listener.onComError(comErrorMessage);
						}
						catch(Exception e)
						{
                            FileLog.w(TAG, e.getMessage(), e);
						}
					}
				}
			});
		}
	}
}
