I'm developing a simple call blocker Android app and was using CallScreeningService quite successfully. I do some database calls to detect numbers to be blocked and start an async task to send some information about the blocked calls to an external server. The implementation is working fine most of the time. But sometimes this fails miserably making the phone ring even after the call is said to be blocked. Following is my code.
#RequiresApi(api = Build.VERSION_CODES.N)
public class CallCaptureService extends CallScreeningService {
#Override
public void onScreenCall(Call.Details details) {
boolean blockCall = false;
String handle = details.getHandle().toString();
handle = handle.replace("tel:", "");
Log.d(SettingsConstants.CALL_LOG_TITLE, String.format("Call event received. Handle='%s'", handle));
long startTime = System.currentTimeMillis();
if (handle != null && !handle.isEmpty()) {
try {
String callingNumber = URLDecoder.decode(handle, "UTF-8");
if (AppUtil.isBlockedNumber(callingNumber)) { //This is a sqlite database call
blockCall = true;
} else if (AppUtil.isKnownPhoneNumber(this, callingNumber)) { //This is again a database call
//Do not block call
}
Log.i(SettingsConstants.CALL_LOG_TITLE, String.format("Sending call information to Call handler"));
Intent callCapturedIntent = new Intent();
callCapturedIntent.putExtra(ExtraKeys.CALLING_NUMBER_EXTRA_KEY, callingNumber);
callCapturedIntent.putExtra(ExtraKeys.CALL_STATE_EXTRA_KEY, CallState.RINGING.getValue());
callCapturedIntent.putExtra(ExtraKeys.CALL_SCREENING_USED_EXTRA_KEY, true);
callCapturedIntent.putExtra(ExtraKeys.ALREADY_KNOWN_NUMBER_EXTRA_KEY, isKnownPhoneNumber);
handleCallForCallScreening task = new handleCallForCallScreening(callCapturedIntent, getApplicationContext());
task.execute();
CallResponse.Builder response = new CallResponse.Builder();
response.setRejectCall(blockCall);
response.setSkipCallLog(blockCall);
response.setSkipNotification(blockCall);
response.setDisallowCall(blockCall);
long endTime = System.currentTimeMillis();
long delay = (endTime - startTime);
Log.i(SettingsConstants.CALL_LOG_TITLE, String.format("(Waiting for call) time for processing %s = %d", handle, delay));
if (blockedCallHangupTime > 0) {
long remainingTime = blockedCallHangupTime - delay;
if (remainingTime > 0) {
try {
Log.i(SettingsConstants.CALL_LOG_TITLE, String.format("Sleeping for %d (ms)", remainingTime));
Thread.sleep(remainingTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
respondToCall(details, response.build());
if (blockCall) {
Log.i(SettingsConstants.CALL_LOG_TITLE, String.format("Call Blocked Successfully"));
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
}
}
Log.d(SettingsConstants.CALL_LOG_TITLE, String.format("Finished handling call event. Handle='%s'", handle));
}
class handleCallForCallScreening extends AsyncTask<Void, Void, Void> {
Context _ctx;
Intent _intent;
public handleCallForCallScreening(Intent intent, Context context) {
_ctx = context;
_intent = intent;
}
#Override
protected Void doInBackground(Void... voids) {
CallHandlerHelper.handleCall(_intent, _ctx); //This method has a network operation, which will call a rest API from a remote server
return null;
}
}
}
And this is the code extract of the method inside our async task. This contains a network operation.
public static void handleCall(#NonNull Intent intent, Context context) {
...
String callingNumber = intent.getStringExtra(ExtraKeys.CALLING_NUMBER_EXTRA_KEY);
boolean callScreeningUsed = intent.getBooleanExtra(ExtraKeys.CALL_SCREENING_USED_EXTRA_KEY, false);
boolean isKnownPhoneNumber = intent.getBooleanExtra(ExtraKeys.ALREADY_KNOWN_NUMBER_EXTRA_KEY, false);
CallState state = CallState.fromInteger(intent.getIntExtra(ExtraKeys.CALL_STATE_EXTRA_KEY, 3));
...
CallSenderResult result = sendCALL(context, callingNumber, requestId, null);
}
private static CallSenderResult sendCALL(Context ctx, String callingNumber, String requestId) {
String appInstanceUniqueKey = StorageHelper.getAppInstanceUniqueKey(ctx);
CallSenderResult result = new CallSenderResult();
try {
if (SettingsConstants.IGNORE_SSL_CERTIFICATE) {
CommunicationRest.disableSSLCertificateChecking();
}
String accessToken = StorageHelper.getAccessToken(ctx);
String phoneNumberKey = StorageHelper.getPhoneNumberKey(ctx);
String refreshToken = StorageHelper.getRefreshToken(ctx);
JSONObject jsonObject = new JSONObject();
jsonObject.put("callingNumber", callingNumber);
jsonObject.put("requestId", requestId);
jsonObject.put("phoneNumberKey", phoneNumberKey);
jsonObject.put("detectedCallingNumbers", detectedCallingNumbersString);
....
Log.i(SettingsConstants.CALL_LOG_TITLE, String.format("Sending CALL information. CallingNumber:'%s', RequestId:'%s', delectedCallingNumbers:'%s'", callingNumber, requestId, detectedCallingNumbers));
String jsonString = jsonObject.toString();
HttpsURLConnection conn = NetworkUtil.getHttpPostURLConnectionForJson(accessToken, jsonString, FirebaseConstants.CALL_RESPONSE_URL);
int responseCode = conn.getResponseCode();
switch (responseCode) {
case 200:
Log.i(SettingsConstants.CALL_LOG_TITLE, String.format("CALL response sent successfully. ReqId:'%s', CallingNumber:'%s', delectedCallingNumbers:'%s'", requestId, callingNumber, detectedCallingNumbers));
result.setState(CallSenderState.SUCCESS);
break;
...
}
} catch (Exception e) {
...
}
return result;
}
public static final int CONNECTION_TIMEOUT_IN_MS = 30 *1000;
public static final int READ_TIMEOUT_IN_MS = 30 *1000;
public static HttpsURLConnection getHttpPostURLConnectionForJson(String accessToken, String json, String urlString) throws IOException {
URL url = new URL(urlString);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
conn.setRequestProperty("Accept", "*/*");
conn.setRequestProperty("authorization", "Bearer " + accessToken);
conn.setDoOutput(true);
conn.setConnectTimeout(SettingsConstants.CONNECTION_TIMEOUT_IN_MS); //
conn.setReadTimeout(SettingsConstants.READ_TIMEOUT_IN_MS);
OutputStream os = conn.getOutputStream();
os.write(json.getBytes("UTF-8"));
os.close();
return conn;
}
This code works fine most of the time and blocks calls if the number is found in our blocked number database. But sometimes this fails even the blocked number is found in our database and the log is there saying that the number is blocked successfully.
This is a log extract from our device when the number is intended to be blocked but actually rang.
2020-03-06 13:15:32.079 DEBUG CALL: Call event received. Handle='%2B85590914400'
2020-03-06 13:15:32.091 INFO CALL: Sending call information to Call handler
2020-03-06 13:15:32.099 INFO CALL: (Waiting for call) time for processing %2B85590914400 = 14
2020-03-06 13:15:32.116 INFO CALL: Sending CALL information. CallingNumber:'+85590914400' // This is printed from async task
2020-03-06 13:15:37.137 INFO CALL: Call Blocked Successfully
2020-03-06 13:15:37.162 DEBUG CALL: Finished handling call event. Handle='%2B85590914400'
2020-03-06 13:15:37.240 INFO CALL: Phone State change detected.(RINGING) //This is where the ringing occurs.
2020-03-06 13:15:37.890 INFO CALL: CLI response sent successfully. ReqId:'1583480726222', CallingNumber:'+85590914400', delectedCallingNumbers:'null' // This is printed from async task
2020-03-06 13:15:38.941 INFO CALL: Phone State change detected.(IDLE)
One thing I noticed is that that there is a 5s delay between "(Waiting for call) time for processing ..." message and "Call Blocked Successfully message". For the situations where this code works perfectly the timegap between these two messages are just few milliseconds. So I doubt whether there is some kind of a hang occurring inside respondToCall message.
Also this issue mostly occurs when I'm on a weak data connection. But the network operation is written inside an async task which may not have any effect on the CallScreeningService behaviour. But not sure about whats happening here.
Related
I'm building a PJSUA2 (PJSIP 2.8) Android app and I have some issues: i.e. only on incoming call, call state remains in "PJSIP_INV_STATE_CONNECTING" and after 32 seconds the call drops.
I'm looking for the cause of the issue since several days, I googled a lot and all what I found is: in most situations this issue is related to NAT management or network issues related to NAT. In a few words: in most cases the called party does not receive the ACK after answering the call.
Finally I was able to log all SIP messages between my app and the SIP server and found that my app receives the ACK from the server, so I suppose it's not a network related issue.
I compiled PJSIP 2.8 with OpenSSL and SRTP support, but without video support (I don't need it at least at the moment). If it makes any difference, the app has a target version 28 and minimum SDK version 19.
I tried several apps on the market and they work fine enough with and without SRTP and with all signaling transports (UDP, TCP, TLS), WebRTC works fine too (tested with SipML5), so I would exclude a server misconfiguration. My app does the same (except SRTP with which I have some issues at the moment).
I tried with a SIP provider too (MessageNet) using UDP and the behaviour is always the same. I tried to use compact SIP messages and it behaves the same, with and without uri parameters, with and without STUN and or ICE and nothing changes. Mobile network and WiFi networks give the same results.
I tried to debug inside PJSIP library too, but without any success, then I tried to follow the code, to understand what I was doing wrong, but it doesn't seem to me there is something evidently wrong.
The following is the code (last version) which initializes PJSIP:
public class SipService extends Service {
private Looper serviceLooper;
private ServiceHandler serviceHandler;
private final Messenger mMessenger = new Messenger(new IncomingHandler());
private LocalBroadcastManager localBroadcast;
private LifecycleBroadcastReceiver lifecycleBroadcastReceiver;
private boolean lastCheckConnected;
private Endpoint endpoint;
private LogWriter logWriter;
private EpConfig epConfig;
private final List<ManagedSipAccount> accounts = new ArrayList<>();
private final Map<String, Messenger> eventRegistrations = new HashMap<>();
#TargetApi(Build.VERSION_CODES.N)
#Override
public void onCreate() {
super.onCreate();
String userAgent = "MyApp";
try {
PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
String appLabel = (pInfo.applicationInfo.labelRes == 0 ? pInfo.applicationInfo.nonLocalizedLabel.toString() : getString(pInfo.applicationInfo.labelRes));
userAgent = appLabel + "/" + pInfo.versionName;
} catch (PackageManager.NameNotFoundException e) {
Log.e("SipService", "Unable to get app version", e);
}
try {
endpoint = new MyAppEndpoint();
endpoint.libCreate();
epConfig = new EpConfig();
// Logging
logWriter = new PJSIPToAndroidLogWriter();
epConfig.getLogConfig().setWriter(logWriter);
epConfig.getLogConfig().setLevel(5);
// UA
epConfig.getUaConfig().setMaxCalls(4);
epConfig.getUaConfig().setUserAgent(userAgent);
// STUN
StringVector stunServer = new StringVector();
stunServer.add("stun.pjsip.org");
epConfig.getUaConfig().setStunServer(stunServer);
// General Media
epConfig.getMedConfig().setSndClockRate(16000);
endpoint.libInit(epConfig);
// UDP transport
TransportConfig udpCfg = new TransportConfig();
udpCfg.setQosType(pj_qos_type.PJ_QOS_TYPE_VOICE);
endpoint.transportCreate(pjsip_transport_type_e.PJSIP_TRANSPORT_UDP, udpCfg);
// TCP transport
TransportConfig tcpCfg = new TransportConfig();
//tcpCfg.setPort(5060);
endpoint.transportCreate(pjsip_transport_type_e.PJSIP_TRANSPORT_TCP, tcpCfg);
// TLS transport
TransportConfig tlsCfg = new TransportConfig();
endpoint.transportCreate(pjsip_transport_type_e.PJSIP_TRANSPORT_TLS, tlsCfg);
endpoint.libStart();
} catch (Exception e) {
throw new RuntimeException("Unable to initialize and start PJSIP", e);
}
ConnectivityManager cm = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
lastCheckConnected = activeNetwork != null && activeNetwork.isConnected();
updateForegroundNotification();
startForeground(MyAppConstants.N_FOREGROUND_NOTIFICATION_ID, buildForegroundNotification());
localBroadcast = LocalBroadcastManager.getInstance(this);
HandlerThread thread = new HandlerThread("ServiceStartArguments",
Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
// Get the HandlerThread's Looper and use it for our Handler
serviceLooper = thread.getLooper();
serviceHandler = new ServiceHandler(serviceLooper);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// Register LifeCycleBroadcastReceiver to receive network change notification
// It seems it's mandatory to do it programmatically since Android N (24)
lifecycleBroadcastReceiver = new LifecycleBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE");
registerReceiver(lifecycleBroadcastReceiver, intentFilter);
}
// Initialization
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
if (prefs != null) {
try {
CodecInfoVector codecs = endpoint.codecEnum();
SharedPreferences.Editor editor = prefs.edit();
for (int i = 0; i < codecs.size(); i++) {
CodecInfo codec = codecs.get(i);
int priority = prefs.getInt("codecs.audio{" + codec.getCodecId() + "}", 0);
try {
endpoint.codecSetPriority(codec.getCodecId(), (short) priority);
codec.setPriority((short) priority);
} catch (Exception e) {
Log.e("SipService", "Unexpected error setting codec priority for codec " + codec.getCodecId(), e);
}
}
} catch (Exception e) {
Log.e("SipService", "Unexpected error loading codecs priorities", e);
}
}
}
#Override
public void onDestroy() {
for (Account acc : accounts) {
acc.delete();
}
accounts.clear();
try {
endpoint.libDestroy();
} catch (Exception e) {
e.printStackTrace();
}
endpoint.delete();
endpoint = null;
epConfig = null;
if (lifecycleBroadcastReceiver != null) {
unregisterReceiver(lifecycleBroadcastReceiver);
}
super.onDestroy();
}
.......
}
And the following is my Account class with creation and registration code:
public class ManagedSipAccount extends Account {
public final String TAG;
private final VoipAccount account;
private final PhoneAccountHandle handle;
private final SipService service;
private final AccountStatus status;
private final Map<Integer, VoipCall> calls = new HashMap<>();
private final Map<String, VoipBuddy> buddies = new HashMap<>();
private AccountConfig acfg;
private List<SrtpCrypto> srtpCryptos = new ArrayList<>();
private AuthCredInfo authCredInfo;
public ManagedSipAccount(SipService service, VoipAccount account, PhoneAccountHandle handle) {
super();
TAG = "ManagedSipAccount/" + account.getId();
this.service = service;
this.account = account;
this.handle = handle;
this.status = new AccountStatus(account.getUserName() + "#" + account.getHost());
acfg = new AccountConfig();
}
public void register(Map<String, String> contactParameters) throws Exception {
StringBuilder contactBuilder = new StringBuilder();
for (Map.Entry<String, String> entry : contactParameters.entrySet()) {
contactBuilder.append(';');
contactBuilder.append(URLEncoder.encode(entry.getKey(), "UTF-8"));
contactBuilder.append("=\"");
contactBuilder.append(URLEncoder.encode(entry.getValue(), "UTF-8"));
contactBuilder.append("\"");
}
StringBuilder logBuilder = new StringBuilder();
logBuilder.append("Registering: ");
logBuilder.append(account.getProtocol().name());
/*logBuilder.append('(');
logBuilder.append(service.getTransport(account.getProtocol()));
logBuilder.append(')');*/
if (account.isEncryptionSRTP()) {
logBuilder.append(" SRTP");
}
if (account.isIce()) {
logBuilder.append(" ICE");
}
Log.d(TAG, logBuilder.toString());
String idUri = "sip:" + account.getUserName();
if (!"*".equals(account.getRealm())) {
idUri += "#" + account.getRealm();
}
else {
idUri += "#127.0.0.1" /*+ account.getHost()*/;
}
acfg.setIdUri(idUri);
acfg.getRegConfig().setRegistrarUri("sip:" + account.getHost() + ":" + account.getPort() + ";transport=" + account.getProtocol().name().toLowerCase());
acfg.getRegConfig().setRetryIntervalSec(account.getRetryInterval());
acfg.getRegConfig().setRegisterOnAdd(false);
acfg.getSipConfig().setContactUriParams(contactBuilder.toString());
// NAT management
acfg.getNatConfig().setSipStunUse(pjsua_stun_use.PJSUA_STUN_USE_DEFAULT);
if (account.isIce()) {
acfg.getNatConfig().setIceEnabled(true);
acfg.getNatConfig().setIceAlwaysUpdate(true);
acfg.getNatConfig().setIceAggressiveNomination(true);
}
else {
acfg.getNatConfig().setSdpNatRewriteUse(1);
}
acfg.getMediaConfig().getTransportConfig().setQosType(pj_qos_type.PJ_QOS_TYPE_VOICE);
if (account.isEncryptionSRTP()) {
acfg.getMediaConfig().setSrtpUse(pjmedia_srtp_use.PJMEDIA_SRTP_MANDATORY);
acfg.getMediaConfig().setSrtpSecureSignaling(0);
//acfg.getMediaConfig().getSrtpOpt().setKeyings(new IntVector(2));
acfg.getMediaConfig().getSrtpOpt().getKeyings().clear();
acfg.getMediaConfig().getSrtpOpt().getKeyings().add(pjmedia_srtp_keying_method.PJMEDIA_SRTP_KEYING_SDES.swigValue());
acfg.getMediaConfig().getSrtpOpt().getKeyings().add(pjmedia_srtp_keying_method.PJMEDIA_SRTP_KEYING_DTLS_SRTP.swigValue());
acfg.getMediaConfig().getSrtpOpt().getCryptos().clear();
StringVector cryptos = Endpoint.instance().srtpCryptoEnum();
for (int i = 0; i < cryptos.size(); i++) {
SrtpCrypto crypto = new SrtpCrypto();
crypto.setName(cryptos.get(i));
crypto.setFlags(0);
srtpCryptos.add(crypto);
acfg.getMediaConfig().getSrtpOpt().getCryptos().add(crypto);
}
}
else {
acfg.getMediaConfig().setSrtpUse(pjmedia_srtp_use.PJMEDIA_SRTP_DISABLED);
acfg.getMediaConfig().setSrtpSecureSignaling(0);
}
authCredInfo = new AuthCredInfo("digest",
account.getRealm(),
account.getAuthenticationId() != null && account.getAuthenticationId().trim().length() > 0 ? account.getAuthenticationId() : account.getUserName(),
0,
account.getPassword());
acfg.getSipConfig().getAuthCreds().add( authCredInfo );
acfg.getIpChangeConfig().setHangupCalls(false);
acfg.getIpChangeConfig().setShutdownTp(true);
create(acfg);
ConnectivityManager cm = (ConnectivityManager)service.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
boolean isConnected = activeNetwork != null && activeNetwork.isConnected();
if (isConnected) {
setRegistration(true);
}
}
#Override
public void onRegStarted(OnRegStartedParam prm) {
super.onRegStarted(prm);
Log.d(TAG, "Status: Registering...");
status.setStatus(AccountStatus.Status.REGISTERING);
service.updateStatus(this);
}
#Override
public void onRegState(OnRegStateParam prm) {
super.onRegState(prm);
try {
Log.d(TAG, "Registration state: " + prm.getCode().swigValue() + " " + prm.getReason());
AccountInfo ai = getInfo();
status.setStatus(ai.getRegIsActive() ? AccountStatus.Status.REGISTERED : AccountStatus.Status.UNREGISTERED);
Log.d(TAG, "Status: " + status.getStatus().name() + " " + super.getInfo().getUri());
service.updateStatus(this);
} catch (Exception e) {
e.printStackTrace();
}
}
.....
}
Finally, how I answer the code at the moment in a class which extends the PJSIP's Call class:
#Override
public void answerCall() {
Log.d(TAG, "Answering call...");
CallOpParam prm = new CallOpParam(true);
prm.setStatusCode(pjsip_status_code.PJSIP_SC_OK);
prm.getOpt().setAudioCount(1);
prm.getOpt().setVideoCount(0);
try {
this.answer(prm);
} catch (Exception e) {
e.printStackTrace();
}
}
I also tried with new CallOpParam(); with just the status code and nothing else, but nothing changes.
One note: I created the IdUri as sip:username#127.0.0.1 because without the host the resulting contact was and I thought that the missing user part may be the cause of the issue or part of it.
The following is the trace of the app <-> my Asterisk server communication during call (linked because of content length exceed).
https://gist.github.com/ivano85/a212ddc9a808f3cd991234725c2bdb45
The ServerIp is an internet public IP, while the MyIp[5.XXX.XXX.XXX] is my phone's public IP.
As you can see from the log, my app sends a 100 Trying, then a 180 Ringing when the phone rings, then the user answers and the app sends a 200 OK. The server replies with a ACK message (I would say it's not a NAT issue, because PJSIP receives the ACK). I see the same from Asterisk.
After this I would expect the call goes from PJSIP_INV_STATE_CONNECTING to PJSIP_INV_STATE_CONFIRMED, but it does not happen, so PJSIP continues to send a 200 OK and receive the ACK every about 2 seconds, until the call times out after 32 seconds and PJSIP disconnects the call (sending a BYE).
I'm starting to think that PJSIP just ignores ACK messages and just has a wrong behaviour. Please help me to understand what is happening here. I would appreciate it so much!
Obviously let me know if you think that more details are needed.
I have a client server model where the client runs on android. It establishes its tls sockets using the following code:.
(Everything the client does to login and relogin)
public class LoginAsync extends AsyncTask<Boolean, String, Boolean>
protected Boolean doInBackground(Boolean... params)
{
try
{
//only handle 1 login request at a time
synchronized(loginLock)
{
if(tryingLogin)
{
Utils.logcat(Const.LOGW, tag, "already trying a login. ignoring request");
onPostExecute(false);
return false;
}
tryingLogin = true;
}
//http://stackoverflow.com/a/34228756
//check if server is available first before committing to anything
// otherwise this process will stall. host not available trips timeout exception
Socket diag = new Socket();
diag.connect(new InetSocketAddress(Vars.serverAddress, Vars.commandPort), TIMEOUT);
diag.close();
//send login command
Vars.commandSocket = Utils.mkSocket(Vars.serverAddress, Vars.commandPort, Vars.expectedCertDump);
String login = Utils.currentTimeSeconds() + "|login|" + uname + "|" + passwd;
Vars.commandSocket.getOutputStream().write(login.getBytes());
//read response
byte[] responseRaw = new byte[Const.BUFFERSIZE];
int length = Vars.commandSocket.getInputStream().read(responseRaw);
//on the off chance the socket crapped out right from the get go, now you'll know
if(length < 0)
{
Utils.logcat(Const.LOGE, tag, "Socket closed before a response could be read");
onPostExecute(false);
return false;
}
//there's actual stuff to process, process it!
String loginresp = new String(responseRaw, 0, length);
Utils.logcat(Const.LOGD, tag, loginresp);
//process login response
String[] respContents = loginresp.split("\\|");
if(respContents.length != 4)
{
Utils.logcat(Const.LOGW, tag, "Server response imporoperly formatted");
onPostExecute(false); //not a legitimate server response
return false;
}
if(!(respContents[1].equals("resp") && respContents[2].equals("login")))
{
Utils.logcat(Const.LOGW, tag, "Server response CONTENTS imporperly formated");
onPostExecute(false); //server response doesn't make sense
return false;
}
long ts = Long.valueOf(respContents[0]);
if(!Utils.validTS(ts))
{
Utils.logcat(Const.LOGW, tag, "Server had an unacceptable timestamp");
onPostExecute(false);
return false;
}
Vars.sessionid = Long.valueOf(respContents[3]);
//establish media socket
Vars.mediaSocket = Utils.mkSocket(Vars.serverAddress, Vars.mediaPort, Vars.expectedCertDump);
String associateMedia = Utils.currentTimeSeconds() + "|" + Vars.sessionid;
Vars.mediaSocket.getOutputStream().write(associateMedia.getBytes());
Intent cmdListenerIntent = new Intent(Vars.applicationContext, CmdListener.class);
Vars.applicationContext.startService(cmdListenerIntent);
onPostExecute(true);
return true;
}
catch (CertificateException c)
{
Utils.logcat(Const.LOGE, tag, "server certificate didn't match the expected");
onPostExecute(false);
return false;
}
catch (Exception i)
{
Utils.dumpException(tag, i);
onPostExecute(false);
return false;
}
}
with the mksocket utility function being:
public static Socket mkSocket(String host, int port, final String expected64) throws CertificateException
{
TrustManager[] trustOnlyServerCert = new TrustManager[]
{new X509TrustManager()
{
#Override
public void checkClientTrusted(X509Certificate[] chain, String alg)
{
}
#Override
public void checkServerTrusted(X509Certificate[] chain, String alg) throws CertificateException
{
//Get the certificate encoded as ascii text. Normally a certificate can be opened
// by a text editor anyways.
byte[] serverCertDump = chain[0].getEncoded();
String server64 = Base64.encodeToString(serverCertDump, Base64.NO_PADDING & Base64.NO_WRAP);
//Trim the expected and presented server ceritificate ascii representations to prevent false
// positive of not matching because of randomly appended new lines or tabs or both.
server64 = server64.trim();
String expected64Trimmed = expected64.trim();
if(!expected64Trimmed.equals(server64))
{
throw new CertificateException("Server certificate does not match expected one.");
}
}
#Override
public X509Certificate[] getAcceptedIssuers()
{
return null;
}
}
};
try
{
SSLContext context;
context = SSLContext.getInstance("TLSv1.2");
context.init(new KeyManager[0], trustOnlyServerCert, new SecureRandom());
SSLSocketFactory mkssl = context.getSocketFactory();
Socket socket = mkssl.createSocket(host, port);
socket.setKeepAlive(true);
return socket;
}
catch (Exception e)
{
dumpException(tag, e);
return null;
}
}
Here is the command listener service that gets started on successful login:
public class CmdListener extends IntentService
protected void onHandleIntent(Intent workIntent)
{
// don't want this to catch the login resposne
Utils.logcat(Const.LOGD, tag, "command listener INTENT SERVICE started");
while(inputValid)
{
String logd = ""; //accumulate all the diagnostic message together to prevent multiple entries of diagnostics in log ui just for cmd listener
try
{//the async magic here... it will patiently wait until something comes in
byte[] rawString = new byte[Const.BUFFERSIZE];
int length = Vars.commandSocket.getInputStream().read(rawString);
if(length < 0)
{
throw new Exception("input stream read failed");
}
String fromServer = new String(rawString, 0, length);
String[] respContents = fromServer.split("\\|");
logd = logd + "Server response raw: " + fromServer + "\n";
//check for properly formatted command
if(respContents.length != 4)
{
Utils.logcat(Const.LOGW, tag, "invalid server response");
continue;
}
//verify timestamp
long ts = Long.valueOf(respContents[0]);
if(!Utils.validTS(ts))
{
Utils.logcat(Const.LOGW, tag, "Rejecting server response for bad timestamp");
continue;
}
//just parse and process commands here. not much to see
}
catch (IOException e)
{
Utils.logcat(Const.LOGE, tag, "Command socket closed...");
Utils.dumpException(tag, e);
inputValid = false;
}
catch(NumberFormatException n)
{
Utils.logcat(Const.LOGE, tag, "string --> # error: ");
}
catch(NullPointerException n)
{
Utils.logcat(Const.LOGE, tag, "Command socket null pointer exception");
inputValid = false;
}
catch(Exception e)
{
Utils.logcat(Const.LOGE, tag, "Other exception");
inputValid = false;
}
}
//only 1 case where you don't want to restart the command listener: quitting the app.
//the utils.quit function disables BackgroundManager first before killing the sockets
//that way when this dies, nobody will answer the command listener dead broadcast
Utils.logcat(Const.LOGE, tag, "broadcasting dead command listner");
try
{
Intent deadBroadcast = new Intent(Const.BROADCAST_BK_CMDDEAD);
sendBroadcast(deadBroadcast);
}
catch (Exception e)
{
Utils.logcat(Const.LOGE, tag, "couldn't broadcast dead command listener... leftover broadacast from java socket stupidities?");
Utils.dumpException(tag, e);
}
}
And here is the background manager that signs you in when you switch from wifi to lte, lte to wifi, or when you come out of the subway from nothing to lte:
public class BackgroundManager extends BroadcastReceiver
{
private static final String tag = "BackgroundManager";
#Override
public void onReceive(final Context context, Intent intent)
{
if(Vars.applicationContext == null)
{
//sometimes intents come in when the app is in the process of shutting down so all the contexts won't work.
//it's shutting down anyways. no point of starting something
return;
}
AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
if(Vars.uname == null || Vars.passwd == null)
{
//if the person hasn't logged in then there's no way to start the command listener
// since you won't have a command socket to listen on
Utils.logcat(Const.LOGW, tag, "user name and password aren't available?");
}
String action = intent.getAction();
if(action.equals(ConnectivityManager.CONNECTIVITY_ACTION))
{
manager.cancel(Vars.pendingRetries);
new KillSocketsAsync().execute();
if(Utils.hasInternet())
{
//internet reconnected case
Utils.logcat(Const.LOGD, tag, "internet was reconnected");
new LoginAsync(Vars.uname, Vars.passwd).execute();
}
else
{
Utils.logcat(Const.LOGD, tag, "android detected internet loss");
}
//command listener does a better of job of figuring when the internet died than android's connectivity manager.
//android's connectivity manager doesn't always get subway internet loss
}
else if (action.equals(Const.BROADCAST_BK_CMDDEAD))
{
String loge = "command listener dead received\n";
//cleanup the pending intents and make sure the old sockets are gone before making new ones
manager.cancel(Vars.pendingRetries);
new KillSocketsAsync().execute(); //make sure everything is good and dead
//all of this just to address the stupid java socket issue where it might just endlessly die/reconnect
//initialize the quick dead count and timestamp if this is the first time
long now = System.currentTimeMillis();
long deadDiff = now - Vars.lastDead;
Vars.lastDead = now;
if(deadDiff < Const.QUICK_DEAD_THRESHOLD)
{
Vars.quickDeadCount++;
loge = loge + "Another quick death (java socket stupidity) occured. Current count: " + Vars.quickDeadCount + "\n";
}
//with the latest quick death, was it 1 too many? if so restart the app
//https://stackoverflow.com/questions/6609414/how-to-programatically-restart-android-app
if(Vars.quickDeadCount == Const.QUICK_DEAD_MAX)
{
loge = loge + "Too many quick deaths (java socket stupidities). Restarting the app\n";
Utils.logcat(Const.LOGE, tag, loge);
//self restart, give it a 5 seconds to quit
Intent selfStart = new Intent(Vars.applicationContext, InitialServer.class);
int pendingSelfId = 999;
PendingIntent selfStartPending = PendingIntent.getActivity(Vars.applicationContext, pendingSelfId, selfStart, PendingIntent.FLAG_CANCEL_CURRENT);
manager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()+Const.RESTART_DELAY, selfStartPending);
//hopefully 5 seconds will be enough to get out
Utils.quit();
return;
}
else
{ //app does not need to restart. still record the accumulated error messages
Utils.logcat(Const.LOGE, tag, loge);
}
//if the network is dead then don't bother
if(!Utils.hasInternet())
{
Utils.logcat(Const.LOGD, tag, "No internet detected from commnad listener dead");
return;
}
new LoginAsync(Vars.uname, Vars.passwd).execute();
}
else if (action.equals(Const.ALARM_ACTION_RETRY))
{
Utils.logcat(Const.LOGD, tag, "login retry received");
//no point of a retry if there is no internet to try on
if(!Utils.hasInternet())
{
Utils.logcat(Const.LOGD, tag, "no internet for sign in retry");
manager.cancel(Vars.pendingRetries);
return;
}
new LoginAsync(Vars.uname, Vars.passwd).execute();
}
else if(action.equals(Const.BROADCAST_LOGIN_BG))
{
boolean ok = intent.getBooleanExtra(Const.BROADCAST_LOGIN_RESULT, false);
Utils.logcat(Const.LOGD, tag, "got login result of: " + ok);
Intent loginResult = new Intent(Const.BROADCAST_LOGIN_FG);
loginResult.putExtra(Const.BROADCAST_LOGIN_RESULT, ok);
context.sendBroadcast(loginResult);
if(!ok)
{
Utils.setExactWakeup(Const.RETRY_FREQ, Vars.pendingRetries);
}
}
}
}
The server is on a select system call to listen to its established sockets. It accepts new sockets using this code (C on Linux)
incomingCmd = accept(cmdFD, (struct sockaddr *) &cli_addr, &clilen);
if(incomingCmd < 0)
{
string error = "accept system call error";
postgres->insertLog(DBLog(Utils::millisNow(), TAG_INCOMINGCMD, error, SELF, ERRORLOG, DONTKNOW, relatedKey));
perror(error.c_str());
goto skipNewCmd;
}
string ip = inet_ntoa(cli_addr.sin_addr);
//setup ssl connection
SSL *connssl = SSL_new(sslcontext);
SSL_set_fd(connssl, incomingCmd);
returnValue = SSL_accept(connssl);
//in case something happened before the incoming connection can be made ssl.
if(returnValue <= 0)
{
string error = "Problem initializing new command tls connection from " + ip;
postgres->insertLog(DBLog(Utils::millisNow(), TAG_INCOMINGCMD, error, SELF, ERRORLOG, ip, relatedKey));
SSL_shutdown(connssl);
SSL_free(connssl);
shutdown(incomingCmd, 2);
close(incomingCmd);
}
else
{
//add the new socket descriptor to the client self balancing tree
string message = "new command socket from " + ip;
postgres->insertLog(DBLog(Utils::millisNow(), TAG_INCOMINGCMD, message, SELF, INBOUNDLOG, ip, relatedKey));
clientssl[incomingCmd] = connssl;
sdinfo[incomingCmd] = SOCKCMD;
failCount[incomingCmd] = 0;
}
The problem I'm having is when the client reconnects to the server from an ip address it has used recently, the socket on the client always seems to die after creation. If I retry again, it dies again. The only way to get it to connect is for the android app to kill and restart itself.
Example: on wifi at home with address 192.168.1.101. Connection ok. Switch to LTE on address 24.157.18.90. Reconnects me to the server ok. Come back home and get 192.168.1.101. The socket always dies until the app kills itself. Or if while I'm outside, I loose LTE because I take the subway, when I come out, I get the same problem. Note that each time, it will make a new socket. It will not somehow try to salvage the old one. The socket creation also seems to succeed. It's just as soon as the client wants to do a read on it, java says the socket is closed.
I put all the relevant code in its unobfuscated original form since it's my hobby project. I am out of ideas why this happens.
// http://stackoverflow.com/a/34228756
//check if server is available first before committing to anything
// otherwise this process will stall. host not available trips timeout exception
Socket diag = new Socket();
diag.connect(new InetSocketAddress(Vars.serverAddress, Vars.commandPort), TIMEOUT);
diag.close();
It is caused by these three pointless lines of code. The server gets a connection and an immediate read() result of zero.
There is no value in establishing a connection only to close it and then assume you can open another one. You should use the conection you just established. In general the correct way to establish whether any resource is available is to try to use it in the normal way. Techniques like the above are indistinguishable from attempts to predict the future.
How to handle if the user resume from disconnect in android
android android-asynctask file-download java io
I am working on an Async downloader
I simply using I/O stream to download
try {
URL url = new URL(myurl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(10000 /* milliseconds */);
conn.setConnectTimeout(15000 /* milliseconds */);
conn.setRequestMethod("GET");
conn.setDoInput(true);
// Starts the query
conn.connect();
int response = conn.getResponseCode();
Log.d(DEBUG_TAG, "The response is: " + response);
is = conn.getInputStream();
// Convert the InputStream into a string
String contentAsString = readIt(is, len);
return contentAsString;
// Makes sure that the InputStream is closed after the app is
// finished using it.
}
And a try catch to alert the message if there is exception. The problem is, if the user connect back the network , how can I resume the download? or say, just execute download task automatically if re-connected? thanks
This has many parts. First, I recommend using droidQuery to handle your network tasks. It is highly configurable and already can handle errors, timeout issues, etc. To get you started, try something like this:
Integer[] statusCodes = new Integer[]{480,522};//request and connection timeout error codes
$.ajax(new AjaxOptions().url(myURL).timeout(1000).dataType("text").statusCode(statusCodes, new Function() {
#Override
public void invoke($ d, Object... args) {
//queue task to run again.
int statusCode = (Integer) args[0];
Log.e("Ajax", "Timeout (Error code " + statusCode + ").");
requeue((AjaxOptions) args[1]);
}
}).success(new Function() {
#Override
public void invoke($ d, Object... args) {
//since dataType is text, the response is a String
String response = (String) args[0];
Log.i("Ajax", "Response String: " + response);
}
}).error(new Function() {
#Override
public void invoke($ d, Object... args) {
//show error message
AjaxError error = (AjaxError) args[0];
Log.e("Ajax", "Error " + error.status + ": " + error.reason);
}
}));
Your requeue method will check the network status. If the network is up, the request is attempted again. If it is unavailable, it will be queued to run when the network is available. This queue will be used:
public static List<AjaxOptions> queue = new ArrayList<AjaxOptions>();
so the method looks something like this:
private void requeue(AjaxOptions options)
{
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = cm.getActiveNetworkInfo();
if (info == null)
cm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
if (info == null)
cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
if (info != null && info.isConnectedOrConnecting())
$.ajax(options);
else {
synchronized(queue) {
queue.add(options);
}
}
}
Finally, to ensure the queued requests are called when the network is available, you need to use a BroadcastReceiver to listen for changes in the network. A good example of this is NetWatcher, but your onReceive method will look more like this:
#Override
public void onReceive(Context context, Intent intent) {
//here, check that the network connection is available. If yes, start your service. If not, stop your service.
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = cm.getActiveNetworkInfo();
if (info != null) {
if (info.isConnected()) {
synchronized(myActivity.queue) {
for (AjaxOptions options : myActivity.queue) {
$.ajax(options);
}
myActivity.queue.clear();
}
}
}
}
If you want the queue variable to be private, you can register the broadcast receiver in code (rather than in the manifest), and make it an inner class of your Activity. Finally, to ensure your BroadcastReceiver works as expected, you need the following permission:
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
Also to ensure internet is available, don't forget the INTERNET permission.
I am developing an android application that consumes web service , the service output is XML
I am connecting to the web service using this code
public String converse(String host, int port, String path)
throws IOException, URISyntaxException {
BufferedReader in = null;
String serviceResponse = null ;
try {
HttpClient client = new DefaultHttpClient();
HttpGet request = new HttpGet();
String serviceUrl = "http://"+host+":"+port+path;
System.out.println("service url "+serviceUrl);
request.setURI(new URI(serviceUrl));
System.out.println("Request "+request.toString());
HttpResponse response = client.execute(request);
in = new BufferedReader
(new InputStreamReader(response.getEntity().getContent()));
StringBuffer sb = new StringBuffer("");
String line = "";
String NL = System.getProperty("line.separator");
while ((line = in.readLine()) != null) {
sb.append(line + NL);
}
in.close();
serviceResponse = sb.toString();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return serviceResponse;
}
when the application launches with WI-FI every thing works fine and when I restart the application with 3G connection it hangs and displays a the following dialog
In addition to this code I am using this method inside another method
public void fillAdapter() {
//calling web service using the mentioned method
}
And this function used inside an async task to fill ListView adapter
protected class AsyncLoading extends AsyncTask<Void,Void, BaseModel[]>{
#Override
protected void onPreExecute(){
pd =ProgressDialog.show(BaseListActivity.this, "loading in progress", "waiting .");
}
#Override
protected BaseModel[] doInBackground(Void... params) {
fillAdapter();
return listItems;
}
#Override
protected void onPostExecute(BaseModel[] doc){
//list.setAdapter(doc);
if(doc != null) {
if(doc.length > 0 ) {
if(doc[0] instanceof Activity)
adapter = new OffersListAdapter(getApplicationContext(),doc);
else if (doc[0] instanceof Offer)
adapter = new OffersListAdapter(getApplicationContext(),doc);
else if (doc[0] instanceof Branch) {
adapter = new BranchListAdapter(getApplicationContext(),doc);
Log.i("Branch"," Added Branch");
}else if (doc[0] instanceof Consolation) {
adapter = new ListAdapter(getApplicationContext(),doc);
adapter.setDisplayImage(false);
}else if ( doc[0] instanceof Event) {
adapter = new EventListAdapter(getApplicationContext(),doc);
}
else
adapter = new ListAdapter(getApplicationContext(),doc);
}//end if doc != null
list.setAdapter(adapter);
}
handler.sendEmptyMessage(0);
}
I saw this post but I don't have a good result I'm working on this problem for 2 days
with my thanks in advance .
Note : this problem often appears the first time the application connects to the service after that if I pressed wait and the application continued then al other activities consuming the web service will work fine
I think you have low speed connection of 3G compared to Wi-Fi.So Use Asynctask to load data from server in seperate thread rather than main thread.It is good idea to show ProgressDialog while fetching the data.
And In some cases Apis will work in Wi-Fi and may not in 3G connection.So test Your url in Device browser also to make it confirm
You should use an AsyncTask for every task that could take some time to be executed. See http://developer.android.com/reference/android/os/AsyncTask.html
The problem i am seeing with your code is you are not doing network operation in Thread.
So if you perform asynchronous operation on Main thread,application will display above dialog if it don't receive response in 5 seconds.In your case 3g connection may be taking more than 5 seconds to return response.
Best bet is include above code in Thread!!
I have solved the problem , that I have a splash screen activity inside which I use C2DM and register the device in that activity using service
registrationIntent.putExtra("app", PendingIntent.getBroadcast(getApplicationContext(), 0, new Intent(), 0));
registrationIntent.putExtra("sender", "someemailaddress#gmail.com");
startService(registrationIntent);
I put this code inside an asyncTask and didn't block the UI
Thanks for every one who tried to help me
I tried to refer similar question on SO, but didn't got any help.
In my android app, I'm planning to implement Recent Quote the user has visited i.e. similar to recently visited pages on web.
Following are the steps I'm following:
1.) Whenever user opens any company view, fetch the company symbols from database
2.) Then store the current symbol along with dateTime in database.
3.) For all symbols fetched from database, Fetch their current value and %Change and display Company name, current value and %Change in a list.
The problem arises in the ASyncTask class as postExecute method doesn't allow it's return type to be any other than void.
Am I doing anything wrong?
Any help will be life saver !!!
String[] rsym,rcmp,rchg;
rdbM = new RecentDBManager(CompanyView.this);
try {
Calendar date1 = Calendar.getInstance();
SimpleDateFormat dateformatter = new SimpleDateFormat(
"dd/MM/yyyy HH:mm:ss");
String currentdate = dateformatter.format(date1.getTime());
rdbM.openDB();
//STEP 1
rsym = rdbM.getRecent_sym();
//STEP 2
rdbM.setData(currentsymbol, currentdate);
rdbM.closeDB();
} catch (Exception e) {
throw new Error(" *** ERROR in DB Access *** "+ e.toString());
}
//STEP 3
for(int i=0;i<rsym.length;i++)
{
DownloadRecentQuote quotetask = new DownloadRecentQuote();
recentquotetask
.execute(new String[] { "http://abc.com/stockquote.aspx?id="
+ rsym[i] });
//CURRENT VALUE and %CHANGE which should be returned from ASyncTask class
rcmp[i]=valuearr[0];
rchg[i]=valuearr[1];
}
list1 = new ArrayList<HashMap<String, String>>();
HashMap<String, String> addList1;
for (int i = 0; i < limit; i++)
{
addList1 = new HashMap<String, String>();
addList1.put(RecentSym_COLUMN, rsym[i]);
addList1.put(RecentCMP_COLUMN, rcmp[i]);
addList1.put(RecentChg_COLUMN, rchg[i]);
list1.add(addList1);
RecentAdapter adapter1 = new RecentAdapter(
CompanyView.this, CompanyView.this, list1);
listrecent.setAdapter(adapter1);
}
private class DownloadRecentQuote extends AsyncTask<String, Void, String> {
/* Fetching data for RecentQuote information */
#Override
protected String doInBackground(String... urls) {
String response = "";
for (String url : urls) {
DefaultHttpClient client = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(url);
try {
HttpResponse execute = client.execute(httpGet);
InputStream content = execute.getEntity().getContent();
BufferedReader buffer = new BufferedReader(
new InputStreamReader(content));
String s = "";
while ((s = buffer.readLine()) != null) {
response += s;
}
} catch (Exception e) {
e.printStackTrace();
}
}
return response;
}
#Override
protected void onPostExecute(String result) {
arr1 = result.split("#");
if (arr1[0].length() != 0) {
if (arr1[0].equals("1")) {
arr = arr1[1].split(";");
//RETURN 2 STRINGS
String valuearr[];
valuearr[0] = arr[3];
valuearr[1] = arr[6].concat("%");
//return valuearr;
}
}
}
postExecute() can't return a value because who or what would it return to? Your original method that invoked the AsyncTask is gone because your AsyncTask is running in the background. It's asynchronous meaning when AsyncTask.execute() returns it's still running in the background, and hence postExecute() can't return a value because there's nothing to return it to.
Instead your AsyncTask needs a reference back to your Activity or some other object so it can post your values back to it. In your code the lines after you call execute() can't be there because your task hasn't finished. Instead you should create a method called updateSymbol( currentPrice, percentChange), move all that code below execute() in there, and in your AsyncTask you should pass a reference to the Activity. Then call updateSymbol( currentPrice, percentChange ) from the onPostExecute() method.
But, be careful if you have a reference back to an Activity it can be destroyed while your doInBackground() is running, and when postExecute() runs it should just drop the results or not attempt to update the UI. For example, the user rotates their phone causing the Activity to be destroyed. I find it best to hold a reference to the AsyncTask in the activity so it can cancel() it if the Activity is destroyed. You can call AsyncTask.cancel() then check if your task was canceled like:
public void postExecute( String result ) {
if( !isCanceled() ) {
// do your updating here
activity.setSymbol( result );
}
}
It's really easy to create a base class for all Activities so you can easily keep track of AsyncTasks running:
public class BaseActivity extends Activity {
List<AsyncTask> runningTasks;
public void onStop() {
for( AsyncTask task : runningTasks ) {
task.cancel(true);
}
}
public AsyncTask start( AsyncTask task ) {
runningTasks.add( task );
return task;
}
public void done( AsyncTask task ) {
runningTasks.remove( task );
}
}
Some quick pointers. You don't need execute( new String[] { "blah" + blah } ). Varargs in Java allow you to do this. execute( "blah" + blah ). You also are catching exceptions and continuing without really handling them. It will be hard when something really happens because your app catches them, and just continues as if nothing happened. If you get an error you might want to provide some feedback to the user and stop trying to execute that process. Stop, show an error to the user, and let them do the next thing. Move the catch blocks to the bottom of the methods.
Essentially, AsyncTask.onPostExecute() is where you do whatever you want to do after AsyncTask's doInBackground() is executed and the execution result gets returned. This should be considered the best practice.
When AsyncTask().execute() is called from the UI thread (note that this method must be called from the UI thread), the Android framework creates a worker thread and starts running whatever you wrote in AsyncTask.doInBackground() on this worker thread. At this point (after calling new AsyncTask().execute()), the UI thread continues to execute code after new AsyncTask().execute(). So now during run time, you have two threads (UI and worker thread) both running simultaneously.
But where and when does the AsyncTask execution result get returned from the worker thread back to the UI thread?
The point where your worker thread (doInBackground()) finishes and returns to the UI thread is AysncTask.onPostExecute(). This method is guaranteed to be called by the framework on the UI thread as soon as AsyncTask finishes. In other words, we don't care where and when AsyncTask.onPostExecute() gets called at run time, we just need to guarantee it will be called ultimately at some stage in the future. This is the reason why this method does not return an execution result - instead, it requires that the execution result gets passed in as the only method parameter from doInBackground().
In addition, the Android API provides a way to return an AsyncTask execution result at coding time, AsyncTask.get():
MyAsyncTask myAsyncTask = new MyAsyncTask();
// This must be called from the UI thread:
myAsyncTask.execute();
// Calling this will block UI thread execution:
ExecutionResult result = myAsyncTask.get();
Bear in mind that AsyncTask.get() will block the calling thread's execution, and you will probably get an ANR exception if you call it on the UI thread. This is the payload of using AsyncTask.get(), by calling it on the UI thread, you are actually making your AsyncTask (worker thread) run synchronously with UI thread (by making UI thread wait). To sum up, this is doable but not recommended.
Just for future reference, because this post is a little old:
I have created an Activity class which has an onStart() method and a separate class for the AsyncTask. Based on my test, after the doInbackground() method the result will be sent to the activity first and after that onPostExecute() will run. This is because based off of logcat, I have my first response data (sent by server) first, then this response will show again from the activity and the last the message in onPostExecute() will show.
Code for the activity:
#Override
protected void onStart() {
super.onStart();
String str = "***";
if(isConnectedToInternet()){
myAsyncTask.execute();
try {
if(myAsyncTask.get())
str = myAsyncTask.getResponseMsg();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (CancellationException e) {
e.printStackTrace();
}
}
Log.i("Data returned by server2:", str);
}
AsyncTask code:
public class MyAsyncTask extends AsyncTask<Void, Void, Boolean> {
private URL url;
private HttpURLConnection conn;
private String strResponseMsg;
public MyAsyncTask(String url) throws MalformedURLException{
this.url = new URL(url);
}
#Override
protected void onPreExecute() {
Log.i("Inside AsyncTask", "myAsyncTask is abut to start...");
}
#Override
protected Boolean doInBackground(Void... params) {
boolean status = false;
try {
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(Manager.ConnTimeout);
conn.setReadTimeout(Manager.ReadTimeout);
int responseCode = conn.getResponseCode();
Log.i("Connection oppened", "Response code is:" + responseCode);
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
if (in != null) {
StringBuilder strBuilder = new StringBuilder();
// Read character by character
int ch = 0;
while ((ch = in.read()) != -1)
strBuilder.append((char) ch);
// Showing returned message
strResponseMsg = strBuilder.toString();
Log.i("Data returned by server:", strResponseMsg);
status = true;
}
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
return status;
}
#Override
protected void onPostExecute(Boolean result) {
Log.i("Inside AsyncTask", "myAsyncTask finished its task. Returning data to caller...");
}
public String getResponseMsg(){
return strResponseMsg;
}
}