I am building a simple Android app to check if active internet connection is available on my phone. For checking this I am using a service which is running into background every 30 seconds and "pings" the Google's DNS server to check if there is a response.
This is my class used for checking internet status:
public class NetworkStatus {
private static final String GOOGLE_DNS_SERVER = "8.8.8.8";
private static final String CLOUDFLARE_DNS_SERVER = "1.1.1.1";
private static final String TAG = "OUTGOING-NET-STATUS";
private static final String RETRY_TAG = "DNS-CHECK";
boolean pingDnsServerSuccessful() {
boolean success = false;
int count = 0;
final int MAX_TRIES = 15;
while (!success && count++ < MAX_TRIES) {
Log.d(RETRY_TAG, "Retry value: " + count + " out of " + MAX_TRIES);
success = isDnsServerReachable(CLOUDFLARE_DNS_SERVER) || isDnsServerReachable(GOOGLE_DNS_SERVER);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (success) {
Log.d(TAG, "Outgoing Internet Traffic is Enabled");
} else {
Log.d(TAG, "Error reaching Outgoing Traffic");
}
return success;
}
private boolean isDnsServerReachable(String dnsServer) {
int connectionTimeout = 250;
try {
return InetAddress.getByName(dnsServer).isReachable(connectionTimeout);
} catch (Exception e) {
Log.d(TAG, "Exception: Error while pinging " + dnsServer + " DNS server: " + e);
}
return false;
}
}
and this is the code which triggers the background timer within the service:
public void startTimer() {
//set a new Timer
timer = new Timer();
networkStatus = new NetworkStatus();
notificationManager = new NotificationManager();
//initialize the TimerTask's job
initializeTimerTask();
//schedule the timer, to wake up every 30 seconds
timer.schedule(timerTask, 1000, 30000); //
}
/**
* it sets the timer to print the counter every x seconds
*/
public void initializeTimerTask() {
timerTask = new TimerTask() {
public void run() {
Log.i("in timer", "in timer ++++ " + (counter++));
if (networkStatus.pingDnsServerSuccessful()) {
Log.i(PING_TAG, "Active Internet Connection");
if (!notificationManager.getLastSentNotificationType().equals(NotificationType.INTERNET_UP)) {
Log.i(NOTIFICATION_TAG, "Internet ON");
notificationManager.sendNotification(getApplicationContext(), NotificationType.INTERNET_UP);
}
} else {
Log.i(PING_TAG, "No Internet Connection");
if (!notificationManager.getLastSentNotificationType().equals(NotificationType.INTERNET_DOWN)) {
Log.i(NOTIFICATION_TAG, "Internet OFF");
notificationManager.sendNotification(getApplicationContext(), NotificationType.INTERNET_DOWN);
}
}
}
};
}
Everything works fine for a random amount of time (minutes/hours) when suddenly the DNS server can't be reached anymore when app is running in background and phone is locked. Activating the phone's screen is immediately resulting into a success while pinging the server.
Here are some logs reflecting the behaviour:
Logs
Does anybody have a clue why is this happening? I highly doubt that Google or Cloudflare DNS server becomes unresponsive...
Many Thanks!
This is Doze mode. To save power, background requests are limited to a small window every 15 minutes or so for requests. In addition, background processes can be killed at any time.
I'm not sure what you're actually trying to do, but there are 100% better ways to do it. For example, JobScheduler allows you to schedule a job to go off only if the internet is connected. No need to ping a server manually, and no need to do all the work to avoid Doze.
Related
I am trying to create a notification service in my android app that always keeps listening to my RabbitMQ server for new messages. I want it to be able to send notifications even from background. Basically I am trying to create a notification communication between two client side application (App1 and App2) through Rabbit MQ and send notifications to both the apps in case of an event.
I have implemented it using JOB Service class but it is not consistent and it stops after sometime. Can someone please help me in understanding the architecture in a better way. How can I achieve something like Firebase Messaging Service but through Rabbit MQ?
Sample code that I have used below:
public class StoreOrderJobService extends JobService {
private static final String TAG = "JobService";
Random random = new Random();
SharedPrefManager prefManager;
private boolean jobCancelled = false;
#Override
public boolean onStartJob(JobParameters jobParameters) {
Log.d(TAG, "Job Started");
prefManager = new SharedPrefManager(this);
subscribeStore(prefManager.getUserId(), jobParameters);
return true;
}
private void subscribeStore(String storeId, JobParameters parameters) {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(HOST_IP);
factory.setAutomaticRecoveryEnabled(false);
String queueName = prefManager.getSessionId();
if (queueName != null) {
Thread subscribeStoreThread = new Thread(new Runnable() {
#Override
public void run() {
Log.d(TAG, "Job Started");
try {
if (jobCancelled) {
return;
}
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
Log.d("OrderService", "Session Id " + queueName);
channel.queueDeclare(queueName, false, false, false, null);
channel.queueBind(queueName, "store_test", storeId);
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
Log.d("OrderService", "Received message " + message);
Envelope envelope = delivery.getEnvelope();
String routingKey = envelope.getRoutingKey();
if (routingKey.equals(storeId)) {
channel.basicAck(envelope.getDeliveryTag(), true);
String message_new = new String(delivery.getBody(), "UTF-8");
Gson gson = new Gson();
OrderSubscribePayload payload = gson.fromJson(message_new, OrderSubscribePayload.class);
Log.d("order Service", "Order Id " + payload.getOrderId());
sendOrderNotification(random.nextInt(), payload);
}
};
channel.basicConsume(queueName, false, deliverCallback, consumerTag -> {
});
} catch (TimeoutException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (RuntimeException e) {
e.printStackTrace();
}
}
});
subscribeStoreThread.start();
}
}
private void sendOrderNotification(int id, OrderSubscribePayload payload) {
Log.d("Service", "sendOrderNotification " + payload.getOrderId());
Intent contextIntent = new Intent(this, OrderDetails.class);
Bundle args = new Bundle();
args.putSerializable("orderDetails", (Serializable) payload);
contextIntent.putExtra("Bundle", args);
int iUniqueId = (int) (System.currentTimeMillis() & 0xfffffff);
PendingIntent pIntent = PendingIntent.getActivity(this, iUniqueId, contextIntent, 0);
Notification n = new NotificationCompat.Builder(this, ManagedApplication.CHANNEL_ORDER_ID)
.setContentTitle("New Order")
.setContentText("Received New Order")
.setSmallIcon(R.drawable.ic_stat_name)
.setContentIntent(pIntent)
.setAutoCancel(true)
.setOnlyAlertOnce(true)
.setColor(getResources().getColor(R.color.color_primary))
.setCategory(NotificationCompat.CATEGORY_REMINDER)
.build();
NotificationManager notificationManager =
(NotificationManager) getSystemService(Service.NOTIFICATION_SERVICE);
notificationManager.notify(id, n);
}
#Override
public boolean onStopJob(JobParameters jobParameters) {
Log.d(TAG, "Job Cancelled");
jobCancelled = true;
return true;
}
}
I am calling this job on users login as below:
private void startNotificationJob() {
ComponentName componentName = new ComponentName(this, StoreOrderJobService.class);
JobInfo info = new JobInfo.Builder(123, componentName)
.setPersisted(true)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setPeriodic(15 * 60 * 1000)
.build();
JobScheduler jobScheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);
int result = jobScheduler.schedule(info);
if (result == JobScheduler.RESULT_SUCCESS) {
Log.d("JOB Scheduler", "Job Scheduled");
} else Log.d("JOB Scheduler", "Job Scheduled Failed");
}
I have implemented it using JOB Service class but it is not consistent and it stops after sometime.
Nothing will be keeping your process running, which you will need for your desired functionality. Nothing will be keeping the CPU powered on all the time, which also will be needed for your desired functionality.
Please bear in mind that what you want is rather harmful to the battery life.
How can I achieve something like Firebase Messaging Service but through Rabbit MQ?
Use a foreground service, with a continuous partial WakeLock, in a separate process (android:process manifest attribute) than the UI of your app. You may also need code to deal with re-establishing your MQ connection as the device changes connectivity (e.g., from WiFi to mobile data) or loses and then regains connectivity.
You will also need to ask your users to go into the Settings app and try to opt your app out of all battery optimizations. Note that this will not be possible on all devices.
You will also need to ask your users to never kill your app's task. The separate process may help on some devices if users forget, but that behavior seems to vary by device.
And you will need to take into account that your solution will not be reliable, because some device manufacturers to prevent developers from doing the sorts of things that you want to do, as those things are bad for battery life.
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 some pics to upload to the ftp server and I am using Asynctask for it.The images need to be sent to multiple host so I am using a for loop.The data to be passed is very well being fetched by the constructor but the doInBackground method is not running which was earlier running very well without the for loop and the additional data apart from the String filePathName that I am trying to pass in now in doInBackground.please help me
class uploadTask extends AsyncTask<String, Void, String> {
public uploadTask(String filePathName,String host_2,String user_2,String pass_2)
{
filePath=filePathName;
host_1=host_2;
user_1=user_2;
pass_1=pass_2;
Toast.makeText(getBaseContext(),"FTP DATA RECEIVING:"+"HOST:"+host_2+" USERNAME:"+user_2+" PASS:"+pass_2,Toast.LENGTH_LONG).show();
//hostName=host;
}
#Override
protected String doInBackground(String... params) {
try {
Toast.makeText(getBaseContext(),"Entered Do in Background Method to upload",Toast.LENGTH_SHORT).show();
ftp_host = "ftp.photoshelter.com";//This is not the correct way. Supposed to get from Backendless table
ftp_username = "brytest";//This is not the correct way. Supposed to get from Backendless table
ftp_password = "passtest";//This is not the correct way. Supposed to get from Backendless table
Toast.makeText(getBaseContext(),"HOST:"+ftp_host+" USERNAME:"+ftp_username+" PASS:"+ftp_password,Toast.LENGTH_LONG).show();
news_agency = "news agency";
easyFTP ftp = new easyFTP();
ftp.connect(ftp_host, ftp_username, ftp_password);
status = ftp.setWorkingDirectory("mem/images"); // if User say provided any Destination then Set it , otherwise
// Upload will be stored on Default /root level on server
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageTimeStamped = ftp_username + "_" + timeStamp + ".png";
FileInputStream is = new FileInputStream(imageFileLocation);
//addPhotoGrapherInfo();
ftp.uploadFile(is, imageTimeStamped);
System.out.println("Successfull ftp upload to " + ftp_host);
Toast.makeText(getBaseContext(), "Photo uploading by ftp to " + ftp_host, Toast.LENGTH_LONG).show();
//}
//reset booleans
//cameraPicTaken = false;
//galleryImageSelected = false;
//System.out.println("reset cameraPicTaken and galleryImageSelected");
// }
return new String("Upload Successful");
}catch (Exception e){
String t="Failure : " + e.getLocalizedMessage();
return t;
}
}
}
my onClickListener with for loop
if(cameraPicTaken || galleryImageSelected) {
Toast.makeText(SubmitActivity.this,"Image Location is:"+ imageFileLocation,Toast.LENGTH_LONG).show();
//addPhotoGrapherInfo();
for(int i=0;i<Common.selectedHostArray.size();i++) {
uploadFile(imageFileLocation,Common.selectedHostArray.get(i),Common.selectedUsernameArray.get(i),Common.selectedPasswordArray.get(i));
}
cameraPicTaken = false;
galleryImageSelected = false;
}
funnction called in onClick
public void uploadFile(String filePath,String host_1,String user_1,String pass_1)
{
if(cameraPicTaken == true) {
System.out.println("camera photo start upload");
//for(int i=0;i<Common.selectedHostArray.size();i++) {
//host_1=Common.selectedHostArray.get(i);
//user_1=Common.selectedUsernameArray.get(i);
//pass_1=Common.selectedPasswordArray.get(i);
//host_1="ftp.photoshelter.com";
//user_1="brytest";
//pass_1="passtest";
Toast.makeText(getBaseContext(),"FTP DATA PASSING:"+"HOST:"+host_1+" USERNAME:"+user_1+" PASS:"+pass_1,Toast.LENGTH_LONG).show();
new uploadTask(filePath,host_1,user_1,pass_1).execute();
// }
//cameraPicTaken = false;
//galleryImageSelected = false;
System.out.println("reset cameraPicTaken and galleryImageSelected");
//cameraPicTaken = false;
}
if(galleryImageSelected == true){
System.out.println("gallery image start upload");
Toast.makeText(getBaseContext(),"FTP DATA PASSING:"+"HOST:"+host_1+" USERNAME:"+user_1+" PASS:"+pass_1,Toast.LENGTH_LONG).show();
new uploadTask(filePath,host_1,user_1,pass_1).execute();
//new uploadTask(filePat)h.execute();
//galleryImageSelected = false;
}
Toast.makeText(getBaseContext(), "Photo uploading by ftp to photoshelter.com" /*+ news_agency*/, Toast.LENGTH_LONG).show();
}
You're trying to perform a UI command on a background thread (Toast). This is causing your background tasks to fail early. Since your background tasks catch their own errors, they fail silently.
#Override
protected String doInBackground(String... params) {
try {
// you can't Toast on a background thread, this should throw an exception
Toast.makeText(getBaseContext(),"Entered Do in Background Method to upload",Toast.LENGTH_SHORT).show();
...
}catch (Exception e){
// your Toast exception is getting caught silently here
String t="Failure : " + e.getLocalizedMessage();
return t;
}
}
By the way, the try/catch on everything is not a good practice. You end up with a ton of silent failures leaving you scratching your head and asking why things aren't working.
I'm working in an application which must send a GPS location every 5 seconds to the server when I choose (auto send button on). I'm new with android so I don't know how I can make an on/off button and how can I invoke the method that sends the data every 5 seconds when the button is on.
The method which it must invoke every 5 seconds:
public void postData() throws ClientProtocolException, IOException, Exception {
String longitude="UK";
String latitude="UK";
String altitiude="UK";
String time="";
String speed="";
getCurrentLocation(); // gets the current location and update mobileLocation variables
if (mobileLocation != null) {
locManager.removeUpdates(locListener); // This needs to stop getting the location data and save the battery power.
longitude = ""+mobileLocation.getLongitude();
latitude = "" + mobileLocation.getLatitude();
altitiude = "" + mobileLocation.getAltitude();
String accuracy = "Accuracy" + mobileLocation.getAccuracy();
time = "" + mobileLocation.getTime();
speed =""+ (int)(4*mobileLocation.getSpeed());
editTextShowLocation.setText(longitude + "\n" + latitude + "\n"
+ altitiude + "\n" + accuracy + "\n" + time+ "\n" + speed);
} else {
editTextShowLocation.setText("Sorry, location is not determined");
}
String url = "http://www.itrack.somee.com/post.aspx?id="+"f1"+"&long="+longitude+"&lat="+latitude+"&alt="+altitiude+"&speed="+speed;
// Create a new HttpClient and Post Header
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(url);
try {
// Execute HTTP Post Request
HttpResponse response = httpclient.execute(httppost);
} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
} catch (IOException e) {
// TODO Auto-generated catch block
}
}
I have faced exactly the same problem, sending location periodically. I've used a handler and its postDelayed method.
The periodic call part of my code looks like this:
private final int FIVE_SECONDS = 5000;
public void scheduleSendLocation() {
handler.postDelayed(new Runnable() {
public void run() {
sendLocation(); // this method will contain your almost-finished HTTP calls
handler.postDelayed(this, FIVE_SECONDS);
}
}, FIVE_SECONDS);
}
Then you just need to call scheduleSendLocation when you want to start your period calls.
Ridcully is right, there is probably no reason to send the current location every 5 seconds. Here is the rational behind that:
You really only care about 2 things about the users location:
Where are they right now?
Have they moved since I got their first location?
So once you get a satisfactory initial location, you can just register to get callbacks whenever the users moves like this:
private LocationListener mLocationListener;
#Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mLocationListener = new LocationListener() {
#Override
public void onLocationChanged(final Location location) {
updateLocation(location);
}
#Override
public void onStatusChanged(final String provider,
final int status, final Bundle extras) {
}
#Override
public void onProviderEnabled(final String provider) {
}
#Override
public void onProviderDisabled(final String provider) {
}
};
}
That being said, you could obviously do what these others have said and run the timer every 5 seconds. The thing is, most good locations take 10-20 seconds to run, so you might only want to run it in that interval. Also FYI, this WILL kill battery
Look at the AlarmManager class http://developer.android.com/reference/android/app/AlarmManager.html and particularly the setRepeating function. Its going to be a bit more complicated than you'd like though.
You can use a Timer. But I think it would be better if you only send the position of it has changed a certain distance from the last one you sent. This way you'd send way less data without losing any information.
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.