How to create a Continues running background file upload in android? - 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
...
"

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

Google Drive Change Subscriptions not working?

I'm trying to get 'Change Subscriptions' to work using the Drive API for Android, but been unsuccessful so far.
Here the simple use case:
2 android devices, both using the same google account
both subscribe to the same 'file of interest' in their drive folder
if the file 'changes', be it from a change performed by one of the two devices or any external source, all devices that subscribed to this file are notified
As far as I understand, this is exactly what 'Change Subscriptions' are supposed to do for me. I'm using play services revision 27.
The problem I have:
A 'file content change' (or some other file event) made locally on one device is never properly propagated to the all other devices that subscribed to the same file.
Does anyone know of any solutions to this issue, or can point my to what I'm doing wrong?
I've written some simple testcode (see below), that only needs a connected googleApiClient, here's what I tested:
1.
device 1 creates a new testfile calling testFileWriteNew() and adds a change subscription to this file using testFileAddAndRemoveSubscription(), the expected log output:
testfile.txt created, driveId=DriveId:CAESABi0AyDAu9XZhVMoAA== resourceId=null
onCompletion; driveId=DriveId:CAESHDBCLXNzaGVuNGlURkFOMGh0ZWtGWU5FeHVTRVUYtAMgwLvV2YVTKAA= resourceId=0B-sshen4iTFAN0htekFYNExuSEU
STATUS_SUCCESS
added subscription to testfile.txt, driveId=DriveId:CAESHDBCLXNzaGVuNGlURkFOMGh0ZWtGWU5FeHVTRVUYtAMgwLvV2YVTKAA= resourceId=0B-sshen4iTFAN0htekFYNExuSEU
2.
device 2 adds a change subscription to the same file using testFileAddAndRemoveSubscription(), the expected log output:
added subscription to testfile.txt, driveId=DriveId:CAESHDBCLXNzaGVuNGlURkFOMGh0ZWtGWU5FeHVTRVUYwgIg9I-GyZRTKAA= resourceId=0B-sshen4iTFAN0htekFYNExuSEU
As expected, the driveId is different on both devices, but the resourceId is the same 0B-sshen4iTFAN0htekFYNExuSEU, so that same 'cloud' file is referenced
3.
If I update the file with some new data via testFileUpdate I get the following on device 1:
testfile.txt updated, driveId=DriveId:CAESHDBCLXNzaGVuNGlURkFOMGh0ZWtGWU5FeHVTRVUYtAMgwLvV2YVTKAA= resourceId=0B-sshen4iTFAN0htekFYNExuSEU
and device 2:
testfile.txt updated, driveId=DriveId:CAESHDBCLXNzaGVuNGlURkFOMGh0ZWtGWU5FeHVTRVUYwgIg9I-GyZRTKAA= resourceId=0B-sshen4iTFAN0htekFYNExuSEU
4.
Unfortunately, the 'change of content' in the onChange method of the service is only triggered locally. A changed done by device 1 never reaches device 2 and vice versa. If I update the file using device 2 I see the following log on device 2 coming from the service:
onChange; driveId=DriveId:CAESHDBCLXNzaGVuNGlURkFOMGh0ZWtGWU5FeHVTRVUYwgIg9I-GyZRTKAA= resourceId=0B-sshen4iTFAN0htekFYNExuSEU
contentChanged
onChange; driveId=DriveId:CAESHDBCLXNzaGVuNGlURkFOMGh0ZWtGWU5FeHVTRVUYwgIg9I-GyZRTKAA= resourceId=0B-sshen4iTFAN0htekFYNExuSEU
metadataChanged
but I never see the onChange method being triggered on device 1, if device 2 triggered a change, which I would expect.
Code:
private boolean testFileWriteNew() {
final DriveFolder folderRoot = Drive.DriveApi.getRootFolder(mGoogleApiClient);
DriveContentsResult contentsResult = Drive.DriveApi.newDriveContents(mGoogleApiClient).await();
if (!contentsResult.getStatus().isSuccess()) {
return false;
}
DriveContents originalContents = contentsResult.getDriveContents();
OutputStream os = originalContents.getOutputStream();
try {
os.write(String.valueOf(System.currentTimeMillis()).getBytes());
MetadataChangeSet originalMetadata = new MetadataChangeSet.Builder().setTitle("testfile.txt").setMimeType("text/plain").build();
// create the file in root
DriveFolder.DriveFileResult fileResult = folderRoot.createFile(mGoogleApiClient, originalMetadata, originalContents, new ExecutionOptions.Builder().setNotifyOnCompletion(true).build()).await();
if (!fileResult.getStatus().isSuccess()) {
return false;
}
// check 'locally created' file, not yet synced to drive
DriveResource.MetadataResult metadataResult = fileResult.getDriveFile().getMetadata(mGoogleApiClient).await();
if (!metadataResult.getStatus().isSuccess()) {
return false;
}
Log.d(TAG, "testfile.txt created, driveId=" + metadataResult.getMetadata().getDriveId().encodeToString() + " resourceId=" + metadataResult.getMetadata().getDriveId().getResourceId());
return true;
} catch (IOException ioe) {
return false;
}
}
private boolean testFileUpdate() {
final DriveFolder folderRoot = Drive.DriveApi.getRootFolder(mGoogleApiClient);
// find testfile
DriveId testFile = null;
MetadataBufferResult folderFilesSyncFolder = folderRoot.listChildren(mGoogleApiClient).await();
if (!folderFilesSyncFolder.getStatus().isSuccess()) {
return false;
} else {
MetadataBuffer bufferMetaData = folderFilesSyncFolder.getMetadataBuffer();
for(int i = 0; i < bufferMetaData.getCount(); ++i) {
final Metadata data = bufferMetaData.get(i);
if(!data.isFolder() && !data.isTrashed() && data.isEditable() && data.getTitle().equalsIgnoreCase("testfile.txt")) {
testFile = data.getDriveId();
break;
}
}
bufferMetaData.release();
}
if(testFile == null) {
return false;
}
// update testfile
DriveFile file = Drive.DriveApi.getFile(mGoogleApiClient, testFile);
DriveContentsResult driveContentsResult = file.open(mGoogleApiClient, DriveFile.MODE_WRITE_ONLY, null).await();
if (!driveContentsResult.getStatus().isSuccess()) {
return false;
}
DriveContents originalContents = driveContentsResult.getDriveContents();
OutputStream os = originalContents.getOutputStream();
try {
os.write(String.valueOf(System.currentTimeMillis()).getBytes());
// commit changes
com.google.android.gms.common.api.Status status = originalContents.commit(mGoogleApiClient, null).await();
if(!status.isSuccess()) {
return false;
}
Log.d(TAG, "testfile.txt updated, driveId=" + file.getDriveId().encodeToString() + " resourceId=" + file.getDriveId().getResourceId());
return true;
} catch (IOException ioe) {
return false;
}
}
private boolean testFileAddAndRemoveSubscription(boolean subscribe) {
final DriveFolder folderRoot = Drive.DriveApi.getRootFolder(mGoogleApiClient);
// find testfile
DriveId testFile = null;
MetadataBufferResult folderFilesSyncFolder = folderRoot.listChildren(mGoogleApiClient).await();
if (!folderFilesSyncFolder.getStatus().isSuccess()) {
return false;
} else {
MetadataBuffer bufferMetaData = folderFilesSyncFolder.getMetadataBuffer();
for(int i = 0; i < bufferMetaData.getCount(); ++i) {
final Metadata data = bufferMetaData.get(i);
if(!data.isFolder() && !data.isTrashed() && data.isEditable() && data.getTitle().equalsIgnoreCase("testfile.txt")) {
testFile = data.getDriveId();
break;
}
}
bufferMetaData.release();
}
if(testFile == null) {
return false;
}
// subscribe & unsubscribe
DriveFile file = Drive.DriveApi.getFile(mGoogleApiClient, testFile);
if(subscribe) {
com.google.android.gms.common.api.Status status = file.addChangeSubscription(mGoogleApiClient).await();
if(!status.isSuccess()) {
return false;
}
Log.d(TAG, "added subscription to testfile.txt, driveId=" + file.getDriveId().encodeToString() + " resourceId=" + file.getDriveId().getResourceId());
return true;
} else {
com.google.android.gms.common.api.Status status = file.removeChangeSubscription(mGoogleApiClient).await();
if(!status.isSuccess()) {
return false;
}
Log.d(TAG, "removed subscription from testfile.txt, driveId=" + file.getDriveId().encodeToString() + " resourceId=" + file.getDriveId().getResourceId());
return true;
}
}
And here the service class:
public class ChangeService extends DriveEventService {
// TAG
private static final String TAG = ChangeService.class.getSimpleName();
#Override
public void onChange(ChangeEvent event) {
final DriveId driveId = event.getDriveId();
Log.e(TAG, "onChange; driveId=" + driveId.encodeToString() + " resourceId=" + driveId.getResourceId());
if(event.hasContentChanged()) { Log.e(TAG, "contentChanged"); }
else if(event.hasMetadataChanged()) { Log.e(TAG, "metadataChanged"); }
else if(event.hasBeenDeleted()) { Log.e(TAG, "beenDeleted"); }
}
#Override
public void onCompletion(CompletionEvent event) {
final DriveId driveId = event.getDriveId();
Log.e(TAG, "onCompletion; driveId=" + driveId.encodeToString() + " resourceId=" + driveId.getResourceId());
switch (event.getStatus()) {
case CompletionEvent.STATUS_CONFLICT: Log.e(TAG, "STATUS_CONFLICT"); break;
case CompletionEvent.STATUS_FAILURE: Log.e(TAG, "STATUS_FAILURE"); break;
case CompletionEvent.STATUS_SUCCESS: Log.e(TAG, "STATUS_SUCCESS "); break;
case CompletionEvent.STATUS_CANCELED: Log.e(TAG, "STATUS_CANCELED "); break;
}
event.dismiss();
}
}
I believe, you are falling into the same trap as many of us did before. I too originally assumed that the 'DriveEventService' takes care of notifications between multiple devices running under the same account. I tried and failed miserably, see here (and notice the resounding silence - since April 2014). I was always getting events on a single device only. So, I actually realized that Change Events work only locally within the GooPlaySvcs instance.
This was more or less confirmed by a comment from Steve Bazyl in this unrelated answer (please read including the 'ORIGINAL POST' paragraph), confirming my theory that both 'Change Events' and 'Completion Events' are local (Completion Events report result of network action - like http response).
So to answer your question. after fighting this for awhile, I had to develop a different strategy:
1/ perform GDAA action (create, update)
2/ wait for a Completion Event indicating your mod has been promoted to the Drive
3/ broadcast GCM message that include ResourceId (not DriveId !) plus optional data (up to 4K) to the registered participants.
4/ 'Registered participants' react to the message and download updated metadata/content, resolving the conflicts.
This solution is from summer 2014 and there may be some other pre-packaged solutions from Google since. I'd be happy myself to hear from people who know if there is more elegant solution.
Quite frankly, I don't understand what is this and this for, if the Completion Events do not timely reflect (notify of) the update from another device.
Good Luck

Downloaded files going missing in Android 4.4.2

We are using the built-in DownloadManager to grab files from our server. If we figure out that there has been an update to that file we delete the local version and re-queue a download from the DownloadManager. This only runs when you fully kill and re-start the app (timely updates to files are not the priority, just that we have all the files and that they get updated whenever we notice it). This system works perfectly on all of my personal testing devices, however, when testing in the api 19 emulator or on my co-worker's HTC One the files will download and then disappear (no longer in the app's external data folder). I've figured out that both are version 4.4.2 of android (where my devices are either 4.4.4 or 4.0.4). It's weird because they will stick around for a time, but then random files will disappear.
Here is some code:
AssetManager setup (setup of output folder)
private AssetManager(Context activity){
if(singleton != null&&IOUtils.hasExternalStorage() != IOUtils.ExtStorageState_OK){
return;
}
context = activity;
external = ContextCompat.getExternalFilesDirs(context, "")[0];
external.mkdirs();
imageFolder = new File(external,imagePath);
imageFolder.mkdirs();
singleton = this;
}
Download code
private static class DownloadObject {
public String ServerID;
public String updated_at;
public Uri image;
public DownloadObject() {
super();
}
public DownloadObject(String ServerID,String updated_at){
super();
this.ServerID = ServerID;
this.updated_at = updated_at;
}
public DownloadObject(Cursor cursor){
super();
this.ServerID = cursor.getString(cursor.getColumnIndex(ObjectDao.Properties.ServerID.columnName));
this.updated_at = cursor.getString(cursor.getColumnIndex(ObjectDao.Properties.UpdatedAt.columnName));
String imageFile = cursor.getString(cursor.getColumnIndex(ObjectDao.Properties.Image.columnName));
this.image = Uri.parse(AssetManager.getSingleton().getImageFolder().getPath()).buildUpon().appendPath(imageFile).scheme("file").build();
}
}
//downloadObjectVector is the fresh list of all objects from the server
//existingObjects is the Cursor from the db that lists all existing object locally
private void SpinOffDownloads(final Vector<DownloadObject> downloadObjectVector,final Cursor existingObjects){
new Thread(new Runnable() {
#Override
public void run() {
int count = 0;
if(existingObjects != null){
count = existingObjects.getCount();
}
if (count>0){
existingObjects.moveToFirst();
do{
final DownloadObject obj = new DownloadObject(existingObjects);
DownloadObject notNeededObject = ArrayUtils.findFirst(downloadObjectVector,new ArrayUtils.Predicate<DownloadObject>() {
#Override
public boolean evaluate(DownloadObject downloadObject) {
return downloadObject.ServerID.equals(obj.ServerID)&&downloadObject.updated_at.compareTo(obj.updated_at) <= 0;
}
});
if (notNeededObject != null){
File imageTest = null;
if(notNeededObject.image != null) {
Uri out = Uri.parse(AssetManager.getSingleton().getImageFolder().getPath()).buildUpon().appendPath(notNeededObject.image.getLastPathSegment()).scheme("file").build();
imageTest = new File(out.getPath());
}else{
Log.v(CLASS_NAME,"object with null image:"+notNeededObject.ServerID);
}
if (imageTest == null||imageTest.exists()) {
downloadObjectVector.remove(notNeededObject);
}else{
if (imageTest != null&&imageTest.exists()&&SHOULD_REPLACE_FILE){
Log.v(CLASS_NAME,"DELETING FILE(missing image):"+imageTest.getAbsolutePath());
imageTest.delete();
}
}
}else{
File imageTest = null;
if(obj.image != null) {
imageTest = new File(obj.image.getPath());
if (imageTest != null&&imageTest.exists()&&SHOULD_REPLACE_FILE){
Log.v(CLASS_NAME,"DELETING FILE(image):"+imageTest.getAbsolutePath());
imageTest.delete();
}
}else{
Log.v(CLASS_NAME,"object with null image:"+obj.ServerID);
}
}
}while(existingObjects.moveToNext());
}
if (existingObjects!= null){
try{
existingObjects.close();
}catch (Exception e){
}
}
DownloadManager dm = (DownloadManager)getSystemService(DOWNLOAD_SERVICE);
for (int i = 0; i < downloadObjectVector.size(); i++) {
try {
DownloadObject dlObj = downloadObjectVector.get(i);
Uri in = dlObj.image;
Uri out = Uri.parse(AssetManager.getSingleton().getImageFolder().getPath()).buildUpon().appendPath(in.getLastPathSegment()).scheme("file").build();
dm.enqueue(new DownloadManager.Request(in).setDestinationUri(out).setTitle(in.getLastPathSegment()));
}catch (Exception e){
Log.w(CLASS_NAME,"Error with Download queued:",e);
}
}
}
}).start();
}
Please let me know if you need any other information or code!
EDIT1
So I decided to elaborate on this a bit more with my testing for this and how the issue manifests itself in the hopes that it will make the picture that much more clear!
I start by loading the app via Android Studio and letting it run long enough to know that all the downloads finish and then I look through the app to see which images are there and which are missing. Most images are there normally. Next I exit the app and use the android task manager to fully kill it. Then I re-launch the app via Android Studio. I then wait to make sure that the downloads finish and watch the LogCat to see what files get deleted manually(normally a couple at maximum). Then I go through the app as see which images are still there/which have been added. It seems that every time new images appear AND new images disappear... And normally the ones that get marked as manually deleted actually get replaced via download properly(i.e. NOT "disappeared").
Please let me know if there are any tests you would like for me to do!
File Observer Test
First of all this is the first time I've used a FileObserver so if I've done something stupid please point it out. Here is my observer code:
external = ContextCompat.getExternalFilesDirs(context, null)[0];
external.mkdirs();
fileObserver = new FileObserver(external.getPath(),FileObserver.ALL_EVENTS) {
#Override
public void onEvent(final int event, final String relPath) {
String msg = "???";
switch (event){
case FileObserver.DELETE:
msg = "FILEOB DELETE relPath:"+relPath;
break;
case FileObserver.DELETE_SELF:
msg = "FILEOB DELETE_SELF relPath:"+relPath;
break;
case FileObserver.MODIFY:
msg = "FILEOB MODIFY relPath:"+relPath;
break;
case FileObserver.MOVE_SELF:
msg = "FILEOB MOVE_SELF relPath:"+relPath;
break;
case FileObserver.MOVED_TO:
msg = "FILEOB MOVED_TO relPath:"+relPath;
break;
case FileObserver.MOVED_FROM:
msg = "FILEOB MOVED_FROM relPath:"+relPath;
break;
case FileObserver.ATTRIB:
msg = "FILEOB ATTRIB relPath:"+relPath;
break;
case FileObserver.CREATE:
msg = "FILEOB CREATE relPath:"+relPath;
break;
default:
msg = "Unknown event:"+event+" at relPath:"+relPath;
}
fileObserverHandler.publish(new LogRecord(Level.INFO,msg));
fileObserverHandler.flush();
}
#Override
public void startWatching() {
super.startWatching();
fileObserverHandler.publish(new LogRecord(Level.INFO,"START WATCHING!!!!"));
fileObserverHandler.flush();
Log.v("FileObserver","START WATCHING!!!");
}
};
fileObserver.startWatching();
I'm using the handler because at first I didn't have the startWatching() override in and wasn't getting any logging at all and the docs say that onEvent happens on its own thread and therefore you should use a handler. It's simply this in the class:
public static Handler fileObserverHandler = new ConsoleHandler();
The ONLY output I get from this at all is "START WATCHING!!!". So I'm guessing I must have done something wrong, because I see it downloading/deleting things... at least it says it is.
The behavior you describe sounds like the system is clearing up those files like a cache.
In your call to getExternalFilesDirs you use "", trying to creating a File/directory with "" can be problematic.
Use null instead of "" in your call to getExternalFilesDirs see if that helps
replace
external = ContextCompat.getExternalFilesDirs(context, "")[0];
with
external = ContextCompat.getExternalFilesDirs(context, null)[0];
It seems this issue may not be related to version 4.4.2 only. After reviewing the Download code over and over, I noticed that the download request does not have a setMimeType setting. Sometimes it appears that DownloadManager deletes files upon completion without setting mime type to download request, on some occasions. By default the server sends the file as its content type as application/x-download. Try adding something like
setMimeType(application/octet-stream);
to DownloadManager.Request(in) or whichever mime type that suits the files being downloaded. Hope this helps.
I think it isn't a problem related with the application logic, but the device you were testing on. I have a tablet with the same problem and I was going crazy... the internal storage (were I save the files) may be damaged...

Running multiple tasks in one activity

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.

Connecting to existing Google Chromecast Session from Android (for generic remote control)

I am creating a generic Chromecast remote control app. Most of the guts of the app are already created and I've managed to get Chromecast volume control working (by connecting to a Chromecast device along side another app that is casting - YouTube for example).
What I've having difficult with is performing other media commands such as play, pause, seek, etc.
Use case example:
1. User opens YouTube on their android device and starts casting a video.
2. User opens my app and connects to the same Chromecast device.
3. Volume control from my app (works now)
4. Media control (play, pause, etc) (does not yet work)
I found the Cast api reference that explains that you can sendMessage(ApiClient, namespace, message) with media commands; however the "message" (JSON) requires the sessionId of the current application (Youtube in this case). I have tried the following, but the connection to the current application always fails; status.isSuccess() is always false:
Cast.CastApi
.joinApplication(mApiClient)
.setResultCallback(
new ResultCallback<Cast.ApplicationConnectionResult>() {
#Override
public void onResult(
Cast.ApplicationConnectionResult result) {
Status status = result.getStatus();
if (status.isSuccess()) {
ApplicationMetadata applicationMetadata = result
.getApplicationMetadata();
sessionId = result.getSessionId();
String applicationStatus = result
.getApplicationStatus();
boolean wasLaunched = result
.getWasLaunched();
Log.i(TAG,
"Joined Application with sessionId: "
+ sessionId
+ " Application Status: "
+ applicationStatus);
} else {
// teardown();
Log.e(TAG,
"Could not join application: "
+ status.toString());
}
}
});
Is is possible to get the sessionId of an already running cast application from a generic remote control app (like the one I am creating)? If so, am I right in my assumption that I can then perform media commands on the connected Chromecast device using something like this:
JSONObject message = new JSONObject();
message.put("mediaSessionId", sessionId);
message.put("requestId", 9999);
message.put("type", "PAUSE");
Cast.CastApi.sendMessage(mApiClient,
"urn:x-cast:com.google.cast.media", message.toString());
Update:
I have tried the recommendations provided by #Ali Naddaf but unfortunately they are not working. After creating mRemoteMediaPlayer in onCreate, I also do requestStatus(mApiClient) in the onConnected callback (in the ConnectionCallbacks). When I try to .play(mApiClient) I get an IllegalStateException stating that there is no current media session. Also, I tried doing joinApplication and in the callback performed result.getSessionId; which returns null.
A few comments and answers:
You can get the sessionId from the callback of launchApplication or joinApplication; in the "onResult(result)", you can get the sessionId from: result.getSessionId()
YouTube is still not on the official SDK so YMMV, for apps using official SDK, you should be able to use the above approach (most of it)
Why are you trying to set up a message yourself? Why not building a RemoteMediaPlayer and using play/pause that is provided there? Whenever you are working with the media playback through the official channel, always use the RemoteMediaPlayer (don't forget to call requestStatus() on it after creating it).
Yes it is possible , First you have to save sesionId and CastDevice device id
and when remove app from background and again open app please check is there sessionId then call bello line.
Cast.CastApi.joinApplication(apiClient, APP_ID,sid).setResultCallback(connectionResultCallback);
if you get success result then need to implement further process in connectionResultCallback listener.
//Get selected device which you selected before
#Override
public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo route) {
// Log.d("Route Added", "onRouteAdded");
/* if (router.getRoutes().size() > 1)
Toast.makeText(homeScreenActivity, "'onRouteAdded :: " + router.getRoutes().size() + " -- " + router.getRoutes().get(1).isSelected(), Toast.LENGTH_SHORT).show();
else
Toast.makeText(homeScreenActivity, "'onRouteAdded :: " + router.getRoutes(), Toast.LENGTH_SHORT).show();*/
if (router != null && router.getRoutes() != null && router.getRoutes().size() > 1) {
// Show the button when a device is discovered.
// Toast.makeText(homeScreenActivity, "'onRouteAdded :: " + router.getRoutes().size() + " -- " + router.getRoutes().get(1).isSelected(), Toast.LENGTH_SHORT).show();
mMediaRouteButton.setVisibility(View.VISIBLE);
titleLayout.setVisibility(View.GONE);
castName.setVisibility(View.VISIBLE);
selectedDevice = CastDevice.getFromBundle(route.getExtras());
routeInfoArrayList = router.getRoutes();
titleLayout.setVisibility(View.GONE);
if (!isCastConnected) {
String deid = MyPref.getInstance(homeScreenActivity).readPrefs(MyPref.CAST_DEVICE_ID);
for (int i = 0; i < routeInfoArrayList.size(); i++) {
if (routeInfoArrayList.get(i).getExtras() != null && CastDevice.getFromBundle(routeInfoArrayList.get(i).getExtras()).getDeviceId().equalsIgnoreCase(deid)) {
selectedDevice = CastDevice.getFromBundle(routeInfoArrayList.get(i).getExtras());
routeInfoArrayList.get(i).select();
ReSelectedDevice(selectedDevice, routeInfoArrayList.get(i).getName());
break;
}
}
}
}
}
//Reconnect google Api Client
public void reConnectGoogleApiClient() {
if (apiClient == null) {
Cast.CastOptions apiOptions = new
Cast.CastOptions.Builder(selectedDevice, castClientListener).build();
apiClient = new GoogleApiClient.Builder(this)
.addApi(Cast.API, apiOptions)
.addConnectionCallbacks(reconnectionCallback)
.addOnConnectionFailedListener(connectionFailedListener)
.build();
apiClient.connect();
}
}
// join Application
private final GoogleApiClient.ConnectionCallbacks reconnectionCallback = new GoogleApiClient.ConnectionCallbacks() {
#Override
public void onConnected(Bundle bundle) {
// Toast.makeText(homeScreenActivity, "" + isDeviceSelected(), Toast.LENGTH_SHORT).show();
try {
String sid = MyPref.getInstance(homeScreenActivity).readPrefs(MyPref.CAST_SESSION_ID);
String deid = MyPref.getInstance(homeScreenActivity).readPrefs(MyPref.CAST_DEVICE_ID);
if (sid != null && deid != null && sid.length() > 0 && deid.length() > 0)
Cast.CastApi.joinApplication(apiClient, APP_ID, sid).setResultCallback(connectionResultCallback);
isApiConnected = true;
} catch (Exception e) {
}
}
#Override
public void onConnectionSuspended(int i) {
isCastConnected = false;
isApiConnected = false;
}
};

Categories

Resources