Running multiple tasks in one activity - android

I have a code that reads my inbox and stores each read SMS as a separate file in a fixed folder which i need to upload on FTP later on. I am using the intent for FTP Upload. My program stucture is something like: onCreate() -> Method1 inbox read -> Delete inbox messages -> Method2 Upload FTP -> Method3 Delete Uploaded Folder -> Further Tasks The problem is that Further Tasks are called before the app is done uploading the folder contents and the server is simply disconnected. I tried calling Method3 in the Further Tasks with the help of a Handler set at delay of 10 minutes but it didn't help as upload may take a lot more time than that also it may not have any files at all to upload so those 10 minutes are wasted. I want the app to wait till the upload is complete. So the question is: What is the proper way of doing this?
EDIT :
The code i am using:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ReadnDeleteSMS();
}
public void FTPUpload(){ //FTPUpload via ANDFTP app
Intent intent = new Intent();
intent.setAction(Intent.ACTION_PICK);
Uri ftpUri = Uri.parse("Server"); //Server Call
intent.setDataAndType(ftpUri, "vnd.android.cursor.dir/lysesoft.andftp.uri");
intent.putExtra("command_type", "upload");
intent.putExtra("ftp_username", "username");
intent.putExtra("ftp_password", "password");
intent.putExtra("ftp_pasv", "true");
intent.putExtra("ftp_resume", "true");
intent.putExtra("ftp_encoding", "UTF-8");
intent.putExtra("progress_title", "Uploading folder ...");
intent.putExtra("local_file1", "/sdcard/ReceivedSMS");
intent.putExtra("remote_folder", "remote folder");
intent.putExtra("close_ui", "true"); //Finally start the Activity
startActivityForResult(intent, RESULT_OK); i++;
customHandler.postDelayed(finalizer, 10*60*1000);}
public void ReadnDeleteSMS(){ //Reads, BackUps and Deletes Inbox Messages
Cursor cursor1 = getContentResolver().query(Uri.parse("content://sms/inbox"), null, null, null, null);
if (cursor1.getCount() > 0) {
while (cursor1.moveToNext()){
int id = cursor1.getInt(0);
String address = cursor1.getString(cursor1.getColumnIndex("address"));
String date = cursor1.getString(cursor1.getColumnIndex("date"));
String SMSDate = DateConversion(date);
String msg = cursor1.getString(cursor1.getColumnIndex("body"));
ReadSMS = address + "\n" + SMSDate + "\n" + msg + "\n";
FileName(zText);
myDate = zText.toString(); zText = new StringBuilder();
fileWrite("/sdcard/ReceivedSMS/" + myDate, ReadSMS);
try{
getApplicationContext().getContentResolver().delete(Uri.parse("content://sms/" + id), null, null);
Toast.makeText(getBaseContext(), "Successfully Deleted", Toast.LENGTH_SHORT).show();}
catch(Exception e){
Toast.makeText(getBaseContext(), "Error Deleting", Toast.LENGTH_SHORT).show();}}}
FTPUpload();}
public String DateConversion (String date){ //Proper Date Format Display
Long timestamp = Long.parseLong(date);
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(timestamp);
return calendar.getTime().toString();}
public void FileName(StringBuilder zText) { //Inbox Message File Name
SimpleDateFormat mSDF = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS");
myDate = mSDF.format(new Date());
myDate = myDate.replace(" ", "");
myDate = myDate.replace("/", "");
myDate = myDate.replace(":", "");
myDate = myDate.replace(".", "");
zText.append(myDate);}
public Runnable finalizer = new Runnable(){ //Main Handler
public void run(){
if (i > 0)
{DeleteDirectory("/sdcard/ReceivedSMS"); i = 0;}
//Some Further Tasks
}
These further tasks are to be called often but if the upload is under execution, no such tasks must perform. The tasks include reading a webpage, String editing and such. These are the main tasks of my app.

Without posting some of the code you are using to do this process is difficult to help you but if your problem is that you have to wait until one task finishes to start another then that seems a job for sendBroadcast and BroadcastReceiver.
Example
Say you handle the logic of the ftp upload in a Service (for simplicity, this logic could be on an AsyncTask or anything similar):
public class RefreshService extends IntentService {
#Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
// Backgroud process here
// ...
// When the task is completed
sendBroadcast(new Intent("com.example.action.REFRESH_COMPLETED"));
}
}
}
In order to catch that com.example.action.REFRESH_COMPLETED you have to provide a BroadcastReceiver.
public class RefreshCompletedReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
// The logic you want to execute right after the background task is completed
}
}
You also have to state that this class should catch the specific action you defined before. To do this, update your manifest and include the receiver:
...
<service
android:name=".RefreshService"
android:exported="false" >
</service>
<receiver android:name=".RefreshCompletedReceiver" >
<intent-filter>
<action android:name="com.example.action.REFRESH_COMPLETED" />
</intent-filter>
</receiver>
...
And that's it. The code in the BroadcastReceiver will execute after the background process finishes. Keep in mind that you'll need to handle an interruption of the process or similar network errors and only call sendBroadcast when the operation was successful.
Additional Info
You can dynamically register the receiver using registerReceiver in the context but you'll have to unregister it (typically on the onDestroy method of your activity) or your app will crash (see the reference).
This helped me a lot with this sort of problems.
Also check this question to choose between Service and AsyncTask.
Check the docs here and here for more info on both.
Disclaimer
I just started programming in android so there might be another way to do this, I just found this way easy to understand and easier to implement.

Related

How to log the user activity for a certain duration after an exception occurs in android java?

I am trying to log the exception plus the user navigation for x amount of time. For example, when my application got an exception I will append it in a text file. Now from that point of time of time, I need to log only for a certain time. e.g., 1 hour. Is it possible to do it? This is the code I wrote to get the exception information and log it in a file.
Please someone help me with this. Thanks in advance.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Thread.setDefaultUncaughtExceptionHandler(handleAppCrash);
}
private Thread.UncaughtExceptionHandler handleAppCrash =
new Thread.UncaughtExceptionHandler() {
#SuppressLint("LongLogTag")
#Override
public void uncaughtException(Thread thread, Throwable ex) {
if (!file.exists()) {
file.mkdir();
}
try {
data = android_version + "#" + Device + "#" + username + "#" + version + "#" + dates + "#" + Logtrace;
File gpxfile = new File(file, fname);
FileWriter writer = new FileWriter(gpxfile,true);
writer.append(data);
writer.flush();
writer.close();
} catch (
Exception e) {
e.printStackTrace();
}
}
};
You are looking for a way to
restart the app
write more logs to the same file that you created in the UncaughtExceptionHandler.
Firstly, to restart the app, you can take the following steps:
Created a pending intent, e.g., in your onCreate (where Intent intent has class scope, already defined, i.e., not just defined within onCreate):
intent = PendingIntent.getActivity(
YourApplication.getInstance().getBaseContext(),
0,
new Intent(getIntent()),
getIntent().getFlags());
After your try/catch in your UncaughtExceptionHandler, start an alarm to trigger your app in some amount of time, e.g., 1 second; and you must follow this with a System.exit();. This is so the current dying app will properly quit, so that in 1 second, when the alarm triggers, it will start the app again (but it won't if the app is still running).
AlarmManager mgr = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 1000, intent);
System.exit(2);
You could optionally store the name of the file (fname in your code) to SharedPreferences (for writing to the same file when the app restarts). Or it could be a fixed hardcoded name that your app knows, and doesn't need to save the name.
You could saved a Boolean in SharedPreferences to let your app know that you are restarting from an uncaught exception.
Secondly, whenever the app starts:
Check the Boolean in SharedPreferences; if it is a normal start, proceed like normal.
If it is a restart after uncaught exception, then retrieve the file name from SharedPreferences (or get it hard coded), then you can write more logs to the file. As in Android, a file is uniquely determined by path and file name. With the same file name, you can open the same file.
Define a background service
To do anything in the background for a prolonged time you should use a service. I'm using a JobService in this example.
<service android:name="org.example.LogService"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE"
android:process=":logprocess"/>
Note the process tag, it is important, because you'll want to kill your current process after an uncaught exception.
public class LogService extends JobService {
private Thread thread;
#Override
public boolean onStartJob(final JobParameters params) {
if (!file.exists()) {
file.mkdir();
}
thread = new Thread(() -> {
try {
data = params.getExtras().getString("data");
File gpxfile = new File(file, fname);
FileWriter writer = new FileWriter(gpxfile,true);
writer.append(data);
//continue to write your logs to the file here for as long as you want. you could copy logcat to the file for example.
writer.flush();
writer.close();
} catch (Exception e) {
e.printStackTrace();
}
jobFinished(params, false);
});
thread.start();
return true;
}
#Override
public boolean onStopJob(JobParameters params) {
thread.interrupt();
return true;
}
}
Call your background service
public void uncaughtException(Thread thread, Throwable ex) {
JobScheduler scheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
PersistableBundle extras = new PersistableBundle();
extras.putString("data", android_version + "#" + Device + "#" + username + "#" + version + "#" + dates + "#" + Logtrace);
JobInfo.Builder builder = new JobInfo.Builder(0, new ComponentName(context, LogService.class))
builder.setExtras(extras);
builder.setOverrideDeadline(1000);
scheduler.schedule(builder.build());
//stop the current process.
Process.killProcess(Process.myPid());
System.exit(10);
}
Killing the current process is optional but recommended, the application could be in a bad state after an uncaught exception. If you want to restart your application you can call startActivity from the LogService.
Note:
You've not given any details on what exactly you want to log for x amount of time, so this code only has a comment where the log collection goes.
Note 2:
This code was adapted from the ACRA project of which I am a maintainer, specifically manifest JobSenderService DefaultSenderScheduler and ProcessFinisher

Send recieving messages to email when app is not active

In my project, I want to send all receiving messages to an email address. I successfully did that when the app is open. but when the app is closed, I can't send themail.
how to detect the message is received when the app is closed.
I used the worker for the email sending.
Also, I want to get the message data to the worker class to send the email.
Worker Class
public class MessageSenderWorker extends Worker {
#NonNull
#Override
public Result doWork() {
try {
new Thread(new Runnable() {
#Override
public void run() {
GMailSender sender = new GMailSender("emailaddredd",
"password");
try {
sender.sendMail("datafrom mesaage", "datafrom mesaage",
"emailaddress", "emailaddress");
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
return Result.SUCCESS;
} catch (Exception e) {
return Result.FAILURE;
}
}
}
My Broadcast Reciever
public class MessageReceiver extends BroadcastReceiver {
private static MessageListener mListener;
#Override
public void onReceive(Context context, Intent intent) {
Bundle data = intent.getExtras();
Object[] pdus = (Object[]) data.get("pdus");
for(int i=0; i<pdus.length; i++){
SmsMessage smsMessage = SmsMessage.createFromPdu((byte[]) pdus[i]);
String message = "Sender : " + smsMessage.getDisplayOriginatingAddress()
+ "Email From: " + smsMessage.getEmailFrom()
+ "Emal Body: " + smsMessage.getEmailBody()
+ "Display message body: " + smsMessage.getDisplayMessageBody()
+ "Time in millisecond: " + smsMessage.getTimestampMillis()
+ "Message: " + smsMessage.getMessageBody();
mListener.messageReceived(message);
NotificationWorker notificationWorker = new NotificationWorker();
PeriodicWorkRequest.Builder dataCheckBuilder = new PeriodicWorkRequest.Builder(notificationWorker.getClass(), 1, TimeUnit.MINUTES).setConstraints(networkConstraints());
PeriodicWorkRequest dataCheckWork = dataCheckBuilder.build();
WorkManager.getInstance().enqueue(dataCheckWork);
}
}
If user turn screen OFF then you must use AlarmManager only (based on my practise) no more than 1 time in 30 minutes. For example you can use custom Broadcast Reciever and call it by AlarmManager. Custom Broadcast Reciever set new Alarm etc. Also you have to create Boot Broadcast Reciever to start Alarm.
To "wake up" a device, because of Doze Mode and app stand-by, the only reliable (and battery-friendly) is to use a high priority Firebase Cloud Message (FCM).
Your app can then handle this message retrieving the email you sent. An option there is that, when you receive the FCM, you enqueue a worker that will then download the message/email.
Everything else will be impacted by Doze Mode and other battery optimization systems in Android. Also, using an FCM is the most battery friendly solution for these cases.
Workers already run on a background thread - you shouldn't create your own inside them. They're also for synchronous work - meaning by the time you return a result, your work should have been completed. Take out all the "new Thread" code and put the contents of the Thread directly in the Worker. You'll also want to read this: https://developer.android.com/topic/libraries/architecture/workmanager/advanced/threading

How to create a Continues running background file upload in android?

I am developing an android application that can upload images one by one of a specific folder to the server from background without interrupting the UI. What I implemented is a intentService calls from the launcher activity with the runtime permissions to read the READ_EXTERNAL_STORAGE. I need to upload all the files in the folder and also need to upload new file that are created or moved to the folder. For that I created a file observer class, which I think is not working properly.
public class DirectoryFileObserver extends FileObserver {
String aboslutePath = "";
public DirectoryFileObserver(String path) {
super(path, FileObserver.ALL_EVENTS);
aboslutePath = path;
Log.i("watch path", path);
}
#Override
public void onEvent(int event, String path) {
Log.i("FileObserver++", "File Created:" + path);
File file = new File(aboslutePath + "/" + path);
List<Skeem> mSkeemList = Skeem.find(Skeem.class, "file = ?", new String[]{file.getAbsolutePath()});
if (mSkeemList.size() == 0) {
Skeem mSkeem = new Skeem();
mSkeem.setUsername(AppConstants.UserEmail);
mSkeem.setFolderName(aboslutePath);
mSkeem.setFile(file.getAbsolutePath());
uploadService uploadService = new uploadService();
uploadService.upload(mSkeem);
}
switch (event) {
case FileObserver.ALL_EVENTS:
Log.d("All", "Path" + path);
break;
case FileObserver.CREATE:
Log.d("Create", "Path" + path);
break;
}
}
}
Also created a broad cast receiver as follows to resume file upload when the net connection is retained
public class InternetConnector_Receiver extends BroadcastReceiver {
public InternetConnector_Receiver() {
}
#Override
public void onReceive(Context context, Intent intent) {
try {
boolean isVisible = BaseApplication
.isActivityVisible();// Check if
// activity
// is
// visible
// or not
Log.i("Activity is Visible ", "Is activity visible : " + isVisible);
// If it is visible then trigger the task else do nothing
if (isVisible == true) {
ConnectivityManager connectivityManager = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectivityManager
.getActiveNetworkInfo();
// Check internet connection and accrding to state change the
// text of activity by calling method
if (networkInfo != null && networkInfo.isConnected()) {
Intent serviceIntent = new Intent(context, uploadService.class);
context.startService(serviceIntent);
} else {
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
and in manifest
<receiver
android:name=".InternetConnector_Receiver"
android:enabled="true">
<intent-filter>
<!-- Intent filters for broadcast receiver -->
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
</intent-filter>
</receiver>
I add all the files to the database with a status false.
File[] files = directory.listFiles();
for (int n = 0; n < skeemList.size(); n++) {
Skeem skeem = skeemList.get(n);
for (int o = 0; o < files.length; o++) {
if (!(skeem.getFile().equalsIgnoreCase(files[o].getAbsolutePath()))) {
if (!files[o].isDirectory()) {
Skeem mSkeem = new Skeem();
mSkeem.setFile(files[o].getAbsolutePath());
mSkeem.setFolderName(directory.getName());
mSkeem.setUsername(email);
upload(mSkeem);
}
}
}
}
Then I starts uploading the files with status false and update table with status true.
When I launch the application, the image upload starts successfully. But after some time it stops. Am I using correct Service to upload files from background? Is there any way to upload the contents of the folder? I have gone through so many sites and links. But I couldn't find the exact solution I needed. Please help me.
With the new Background Limitations for Service execution you need to be very cautious with background services.
Instead of an IntentService I recommend you to use a JobIntentService as Google suggests. Pre-Oreo it acts as an IntentService, on (Post-)Oreo it uses Jobs to do background work. Remember to request WAKE_LOCK permission
If you are open to use GitHub library -
https://github.com/gotev/android-upload-service 2.3K stars Apr 2020
"
-Easily upload files (Multipart/Binary/FTP out of the box) in the background with progress indication notification
-upload files to a server with FTP, HTTP multipart/form-data or binary requests
-handle multiple concurrent uploads in the background, even if the device is idle (Doze mode)
-automatically retry failed uploads, with a configurable exponential backoff
possibility to automatically delete uploaded files when the upload is successful
Apps and libraries powered by this library-
-JIRA Cloud
-Quora
...
"

Android DownloadManager not working when redirected

I'm working on an app that will download a zip file stored on Amazon S3 via a Rails Heroku server after authenticating via oAuth 2. Here's the flow:
Request to authenticate with the server running on Heroku via
oAuth2.
Receive oAuth2 access token.
Request to download the zip file from the server (passing the
oAuth token as bearer).
The server authorizes the request and redirects to an Amazon S3
URL containing a expiring signature (to stop anyone downloading the
content without being authenticated).
At this point, I want the DownloadManager to just follow the redirect and get the zip file from S3, however it's failing. Is there some way I can work around this? Or is it just a limitation of DownloadManager?
I'm new to Android and still not totally up on the best debugging methods, so I don't have a lot of output to show you. However, it seems that DownloadManager.COLUMN_STATUS == DownloadManager.STATUS_FAILED and DownloadManager.COLUMN_REASON is returning "placeholder"!
EDIT - Here is the code I'm using. Edited to hide the client etc...
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
Log.i("ChapterListActivity", "Item clicked: " + id);
final DownloadManager downloadManager = (DownloadManager)getSystemService(Context.DOWNLOAD_SERVICE);
Uri uri = Uri.parse("http://myapphere.herokuapp.com/api/v1/volumes/2.zip");
DownloadManager.Request request = new Request(uri);
String accessToken = getSharedPreferences("keyhere", MODE_PRIVATE).getString("access_token", null);
Log.i("SLEChapterListActivity", "Getting file with access token... " + accessToken);
request.addRequestHeader("Authorization", "Bearer " + accessToken);
long reference = downloadManager.enqueue(request);
IntentFilter filter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
long downloadReference = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
Log.i("ChapterListActivity", "Download completed");
Query query = new Query();
query.setFilterById(downloadReference);
Cursor cur = downloadManager.query(query);
if (cur.moveToFirst()) {
int columnIndex = cur.getColumnIndex(DownloadManager.COLUMN_STATUS);
if (DownloadManager.STATUS_SUCCESSFUL == cur.getInt(columnIndex)) {
String uriString = cur.getString(cur.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
File mFile = new File(Uri.parse(uriString).getPath());
} else if (DownloadManager.STATUS_FAILED == cur.getInt(columnIndex)){
String statusResult = cur.getString(cur.getColumnIndex(DownloadManager.COLUMN_REASON));
Toast.makeText(context, "FAILED " + statusResult, Toast.LENGTH_SHORT).show();
} else if (DownloadManager.ERROR_TOO_MANY_REDIRECTS == cur.getInt(columnIndex)){
String statusResult = cur.getString(cur.getColumnIndex(DownloadManager.COLUMN_REASON));
Toast.makeText(context, "TOO MANY REDIRS " + statusResult, Toast.LENGTH_SHORT).show();
}
}
}
};
registerReceiver(receiver, filter);
}
I've found in Download Manager sources (line 500):
3xx: redirects (not used by the download manager)
It's not supported, yet.
In my current project, downloads are made in two steps:
Get Amazon url from our own server via oAuth2
Enqueue DownloadManager with the Amazon url.
If you don't like the two step process, I don't, then take a look at RoboSpice project, it has similar philosophy as DownloadManager.
Just answering a sub-part of this question. The reason why you get the reason as a "placeholder" String is because the reason column is an integer, not a String. See Android DownloadManager: Download fails, but COLUMN_REASON only returns “placeholder”.

Store Battery Level Status In a txt File

I am a New B to Android. I have been Able to get the Battery Status/Level with the Following Code:
private void BattStatus() {
BroadcastReceiver batteryLevelReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
context.unregisterReceiver(this);
int rawlevel = intent.getIntExtra("level", -1);
int scale = intent.getIntExtra("scale", -1);
int level = -1;
if (rawlevel >= 0 && scale > 0) {
level = (rawlevel * 100) / scale;
}
batteryLevel = level;
BattStatus.setText("Battery Level : " + batteryLevel + "%");
}
};
IntentFilter batteryLevelFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
registerReceiver(batteryLevelReceiver, batteryLevelFilter);
}
I would Like to Store the Battery Level In A text file (Using a thread). Code :
public final Runnable DBThread = new Runnable() {
String AllInfo = batteryLevel+"%"+" , "+new SimpleDateFormat("HH:mm , dd.MM.yy ").format(new Date());
public void run() {
try {
Log.d("DBThread","Battery :"+batteryLevel);
Log.d("DBThread","Updating DB");
myDbHelper.CreateAndWriteFile(sdDir+"/", AllInfo );
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mHandler.postAtTime(this, SystemClock.uptimeMillis() + 2000);
Log.d("DBThread","Updated DB");
Log.d("DBThread",AllInfo);
}
Unfortunately the Battery Status/Level returns 0% in the text file, when I test it using the Log function in the thread it returns the correct value.
Please could some one be so kind to tell me what I am doing wrong or what I am not doing, and maybe provide me with a code snippet as I am new to Development.And Sorry If My Post is incorrect First timer on Stack Overflow :)
Thank you very much!
This is not a Thread. This is a Runnable, which is a piece of code that is made to run in a Thread.
In your case, it runs in the handler thread, most likely the UI Thread.
You probably start your thread before receiving the battery status, hence writing the default value (0) to the file.
You don't need a thread for that. You can write to the file immediately after your receive the broadcast, in the onReceive method.
Edit
There are a few things that don't work in your code. You unregister the broadcast receiver, hence you don't receive the battery level after the first time.
You write the batteryLevel value without knowing if it has indeed been modified
You write every 2 seconds without knowing there has been a change.
I would suggest that you don't unregister the BR, so you receive all battery level change. Then, in the onReceive, you append to the file the date and new value.

Categories

Resources