package dt.call.aclient.background.async;

import android.content.Context;
import android.os.AsyncTask;

import java.security.cert.CertificateException;

import dt.call.aclient.CallState;
import dt.call.aclient.Const;
import dt.call.aclient.R;
import dt.call.aclient.Utils;
import dt.call.aclient.Vars;

/**
 * Created by Daniel on 1/30/16.
 */
public class CallEndAsync extends AsyncTask<String, String, Boolean>
{
	private static final String tag = "CallEndAsync";

	@Override
	protected Boolean doInBackground(String... params)
	{
		boolean result;
		try
		{
			//CallMain.onStop turns out to be the only place that calls this async. however, this async will actually be called twice
			// 1: user ends call --> onStop, 2: android detects the screen is going away --> onStop.
			// the second one sends a bogus ts|end|(nobody)|sessionid command because the Vars.callwith has already been cleared after the
			// first onStop. In that case, recognize the second onStop and don't do it. Don't waste resources redoing a redone socket
			if(Vars.callWith.equals(Const.nobody))
			{
				return true;
			}

			//tell the server to end the call
			String endit = Const.JBYTE + Utils.generateServerTimestamp() + "|end|" + Vars.callWith.getName() + "|" + Vars.sessionid;
			Utils.logcat(Const.LOGD, tag, endit);
			Vars.commandSocket.getOutputStream().write(endit.getBytes());

			//reset the media socket to kill the media read/write threads
			//also need to kill the media socket to flush out any data still in its buffers so the next call
			//doesn't play a few seconds off the previous call, followed by the next call or worse...
			//the old call data causes a "frameshift mutation" of the new call data and produces alien morse code
			Utils.logcat(Const.LOGD, tag, "Killing old media port");
			Vars.mediaSocket.close();

			Utils.logcat(Const.LOGD, tag, "Making new media port");
			Vars.mediaSocket = Utils.mkSocket(Vars.serverAddress, Vars.mediaPort, Vars.expectedCertDump);
			String associateMedia = Const.JBYTE + Utils.generateServerTimestamp() + "|" + Vars.sessionid;
			Utils.logcat(Const.LOGD, tag, associateMedia);
			Vars.mediaSocket.getOutputStream().write(associateMedia.getBytes());

			result = true;
		}
		catch (CertificateException c)
		{
			Utils.logcat(Const.LOGE, tag, "Tring to reestablish media port but somehow the certificate is wrong");
			result = false;
		}
		catch (Exception i)
		{
			Utils.dumpException(tag, i);
			result = false;
		}

		//clean up the internal variables
		Vars.state = CallState.NONE;
		Vars.callWith = Const.nobody;

		Utils.updateNotification(Vars.applicationContext.getString(R.string.state_popup_idle), Vars.go2HomePending);
		return result;
	}
}

package dt.call.aclient.background.async;

import android.content.Context;
import android.os.AsyncTask;
import android.os.SystemClock;

import java.security.cert.CertificateException;

import dt.call.aclient.CallState;
import dt.call.aclient.Const;
import dt.call.aclient.R;
import dt.call.aclient.Utils;
import dt.call.aclient.Vars;

/**
 * Created by Daniel on 1/30/16.
 */
public class CallEndAsync extends AsyncTask<String, String, Boolean>
{
	private static final String tag = "CallEndAsync";

	@Override
	protected Boolean doInBackground(String... params)
	{
		boolean result;
		try
		{
			//CallMain.onStop turns out to be the only place that calls this async. however, this async will actually be called twice
			// 1: user ends call --> onStop, 2: android detects the screen is going away --> onStop.
			// the second one sends a bogus ts|end|(nobody)|sessionid command because the Vars.callwith has already been cleared after the
			// first onStop. In that case, recognize the second onStop and don't do it. Don't waste resources redoing a redone socket
			if(Vars.callWith.equals(Const.nobody))
			{
				return true;
			}

			//tell the server to end the call
			String endit = Const.JBYTE + Utils.generateServerTimestamp() + "|end|" + Vars.callWith.getName() + "|" + Vars.sessionid;
			Utils.logcat(Const.LOGD, tag, endit);
			Vars.commandSocket.getOutputStream().write(endit.getBytes());
			SystemClock.sleep(1000); //give some time for the command to reach the server before "pulling the plug"

			//reset the media socket to kill the media read/write threads
			//also need to kill the media socket to flush out any data still in its buffers so the next call
			//doesn't play a few seconds off the previous call, followed by the next call or worse...
			//the old call data causes a "frameshift mutation" of the new call data and produces alien morse code
			Utils.logcat(Const.LOGD, tag, "Killing old media port");
			Vars.mediaSocket.close();

			Utils.logcat(Const.LOGD, tag, "Making new media port");
			Vars.mediaSocket = Utils.mkSocket(Vars.serverAddress, Vars.mediaPort, Vars.expectedCertDump);
			String associateMedia = Const.JBYTE + Utils.generateServerTimestamp() + "|" + Vars.sessionid;
			Utils.logcat(Const.LOGD, tag, associateMedia);
			Vars.mediaSocket.getOutputStream().write(associateMedia.getBytes());

			result = true;
		}
		catch (CertificateException c)
		{
			Utils.logcat(Const.LOGE, tag, "Tring to reestablish media port but somehow the certificate is wrong");
			result = false;
		}
		catch (Exception i)
		{
			Utils.dumpException(tag, i);
			result = false;
		}

		//clean up the internal variables
		Vars.state = CallState.NONE;
		Vars.callWith = Const.nobody;

		Utils.updateNotification(Vars.applicationContext.getString(R.string.state_popup_idle), Vars.go2HomePending);
		return result;
	}
}

Allow a 1 second delay between sending the call end command and killing the socket. Otherwise the socket might be killed before the command makes it to the server. The other person will not know you ended the call and get the silent treatment.