I am trying to download a file (size - 4 MB, extenssion - .apk). When download completes, I am giving it to Android's install manager to do it's job. Now my problem is, if I download the file multiple times (4 -5 times), my phone gets restarted.
UI Flow
I have a button with "Download" as text, on click of it, I execute the below code:
static void downloadApp(Activity context, StorageReference storageRef, String fileName) throws IOException {
File localFile = new File(Environment.getExternalStorageDirectory() + File.separator + fileName);
localFile.createNewFile();
storageRef.child(fileName)
.getFile(localFile)
.addOnSuccessListener(taskSnapshot -> installApp(context, localFile))
.addOnFailureListener(Crashlytics::logException);
}
And on success of the download, I am trying to install the app as below:
private static void installApp(Activity context, File apkFile) {
Intent installIntent = new Intent(Intent.ACTION_VIEW);
installIntent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
context.startActivity(installIntent);
}
When the install manager opens, I click "Cancel" button to go back and click on "Download" button again.
If I repeat the step for 4 times (sometimes 5), my phone reboots.
Please let me know where I am doing wrong. I am guessing it as a defect from Firebase SDK, as I have followed the Documentation guide carefully here. And even though something wrong is there in my code, it should throw any Exception or in worst case an Error (maybe OutOfMemory), but instead it restarts. (A very bad experience for my users)
Related
I have made an app for Android which saves results for skeet shooting. During a session, the user either presses hit or miss. When the session is over, the user press save and the new result is appended to the json-object. After that the result is appended, it is saved to the phone via
public static void saveData(Context context) {
File path = context.getFilesDir();
File file = new File(path, "jsonUsr.json");
if (file.exists()) {
try {
FileOutputStream stream = new FileOutputStream(file);
String objString = usrObject.toString();
stream.write(objString.getBytes());
stream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Now, my friend who has my app used it when we were at a competition today. During the session when my app was running and he had started to fill in his result, he receives an sms. He opens the message and reads it. Then instead of reopen my app from the current apps running, he goes to the meny and presses the icon. Suddenly he discovers that all data is gone! Not just the current session, but all results he has entered. I cannot understand that, because there does not even exists in the code a call which deletes the saved json string file.
I have tried to imitate what he did on my phone, but it works perfectly. He has had a lot of problems with the memory with his phone. For a couple of days ago, it complained about that there were not enough memory for upgrading, so he moved things to the SD-card? Is it possible that the data has either been removed due to lack of memory or that it is moved to the SD card?
It is not so much to work with, but I do not have more. Since I cannot recreate it myself, it is hard to know exactly what has happened.
It is a good idea to always write to a new file in the same directory. If that write succeeds, move the new file onto the config file by changing its name. (this change is atomic)
That way, you will always end up with a valid file, even if the call to write fails for some reason (out of disk, toString() fails, etc.).
Is there an easy mechanism to determine when a DownloadManager remove() has completed, as it appears to be partially asynchronous. The function returns almost instantaneously with a count of the entries in the Download table it's deleted, but the actual filesystem housekeeping appears to be pushed into some background thread.
Issue is I've written a little bit of code that looks for and deletes any existing DownloadManager entry for file X (and hopefully the filesystem object), before pulling a new copy. Unfortunately the new copy is making it to the directory before the filesystem housekeeping has kicked in, for the previous incarnation. So the housekeeping actually ends up deleting the new version at some point and leaving an orphan entry in the DownloadManager table.
Could do with some way to block till the filesystem delete is actioned.
Debug code:
DownloadManager.Query query = new DownloadManager.Query().setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL);
downloads = getAllDownloadIds(manager.query(query));
path = activity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
//If a file name was passed remove any existing entry / version of the file
if ( fileName != null && ! fileName.isEmpty() ){
if ( downloads.containsKey(fileName)){
ids = downloads.get(fileName);
for (Long id : ids) {
Uri path = manager.getUriForDownloadedFile(id);
File checkFile = new File(path.toString());
Log.e(TAG, "Removing existing file: " + path.toString() + ":" + id);
int entriesRemoved = manager.remove(id);
Log.e(TAG, "Existing files removed: " + entriesRemoved);
}
}
}
...
Log.v(TAG, "Attempting to create a file in the 'Download' directory on the external storage::" + path.toString() +"/"+ fileName);
file = new File(path, fileName);
Log.v(TAG, "Does the file already exist::" + file.exists());
Example output:
… V/Export﹕ Removing existing file: file:///storage/sdcard/Download/appData.csv:101
… V/Export﹕ Existing files removed: 1
… V/Export﹕ Attempting to create a file in the 'Download' directory on the external storage::/storage/sdcard/Download/appData.csv
… V/Export﹕ Does the file already exist::true
I had this same problem - when replacing small files in a fast network, the replacement files would sometimes arrive within a fraction of a second after calling DownloadManager.remove(...) In this case, the newly arrived files would be deleted.
The solution I'm using is, before I call DownloadManager.remove(...), I setup up a FileObserver to monitor the file. I then call remove(...), but then wait for the DELETE event to fire before initiating the replacement download.
This ended being a significant amount of code distributed among multiple classes. There are other complicating factors - for example I put a timeout mechanism in just in case the DownloadManager never deletes the file. (I can't imagine why it wouldn't, but it's not my component).
So in answer to the question "Is there an easy mechanism.....": There are mechanisms available to you, but unfortunately not particularly easy ones.
The way i'm solved this timing problem is just to delete the file before DownloadManager.remove function. The reference to the download will be removed by the "remove" function anyway.
int idFromCursor = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_ID));
String localPathOfFile =cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
File fileToDelete= new File(localPathOfFile);
if(fileToDelete.exists()){
fileToDelete.delete();
}
downloadManager.remove(idFromCursor);
after that there was no timing problems anymore.
I'm not exactly sure what my problem is. I using the code below to download a bunch of files:
public void DownloadNow(ArrayList<String> must_download)
{
String serviceString = Context.DOWNLOAD_SERVICE;
DownloadManager download;
download = (DownloadManager)getSystemService(serviceString);
for(int x = 0; x < must_download.size(); x++)
{
System.out.println("DOWNLOADING "+must_download.get(x).toString());
Uri uri = Uri.parse("http://drm.csdev.nmmu.ac.za/Zandre/"+must_download.get(x).toString());
DownloadManager.Request request = new DownloadManager.Request(uri);
request.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, must_download.get(x).toString());
request.setNotificationVisibility(Request.VISIBILITY_VISIBLE);
request.setTitle(must_download.get(x).toString());
long reference = download.enqueue(request);
}
}
Where must_download is an ArrayList full of file names. This method has successfully worked in the past, until I tried to run it on a different thread. The thread idea did not work out so well, so I decided to switch back.
Now, the problem is this- since I switched back to the original method the Download Manager runs full time, downloading the same files over and over in an endless loop.The only way I can stop it is to **Disable the Download Manager, but then I can't download anything...
What I've read so far and tried is
Uninstall the app- still downloading...
Switch device off and back on again- still downloading...
Clear cache of Download Manager- still downloading...
Even after this has used all the devices mobile data, it still just carries on downloading.
Has anyone else encountered this type of problem? Any ideas solutions?
I'm having an issue with opening internal data files in native applications
Its properly important to point out that I'm fairly new to Android development but not new to programming
Setup
I have a mobile Air application which i am running on an Android device. To load PDF's/ play videos within the application I have written a native extension to load the files via their native application.
Problem
When testing the app i found that file's stored in the external storage were loading fine and files in the internal storage were presenting messages from the native applications like cannot play file or cannot open file.( files in internal storage are downloaded and saved on the Air application end ).
This lead me to think its the permission setup within Android.
I know that files within the internal storage are private by default
I have read how to write to a file setting its permission using openFileOutput
but as the file already exists this won't work. I could load the file in and spit it out again but this isn't ideal as will result in what might be unnecessary overhead.
I'm not sure how to proceed, do i need to set a manifest properties on the Air App side? Android App side? both sides? If so which one and where
Or is their a way to change it at run-time, i found the setReadable function but its at API level 9 and i am idealy aiming a little lower than that.
Any help is greatly appreciated
public static void openFile( Activity parentActivity, String filePath, String fileType, String mimeType ) {
//Create the file we are to create
File fileToOpen = new File(filePath);
//Check if the file exists
if( fileToOpen.exists() ) {
//The path of the file we want to open
Uri path = Uri.fromFile(fileToOpen);
//Create a new intent of the file we want to view
Intent intent = new Intent(Intent.ACTION_VIEW);
//Set the path and the mime type for the file
intent.setDataAndType(path, mimeType);
//Remove any other activities
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
//Check that its within the try and catch block
try {
//Open the file by stating a new activity
parentActivity.startActivity(intent);
}
catch (ActivityNotFoundException e) {
//Make a pop-up informing that we don't have an application to open the file
Toast.makeText( parentActivity,"No Application Available to View " + fileType,Toast.LENGTH_SHORT).show();
}
} else {
//Display an alert which will show that the file dosn't exist
Toast.makeText( parentActivity, fileType+" file dosn't exist", Toast.LENGTH_SHORT).show();
}
}
I'm trying to delete a temporary file after sharing it via android's Intent.ACTION_SEND feature. Right now I am starting the activity for a result and in OnActivityResult, I am deleting the file. Unfortunately this only works if I am debugging it with a breakpoint, but when I let it run freely and say, email the file, the email has no attachment.
I think what is happening is my activity is deleting the file before it had been emailed. What I don't get is why, shouldn't onActivityResult only be called AFTER the other activity is finished?
I have also tried deleting the file in onResume, but no luck.
Is there a better way to do this?
I noticed the same behavior with a similar approach. While watching logcat for errors I saw gmail complaining that it couldnt find the attachment. So, yes, it seems the intent returns BEFORE gmail has actually read the file for attachment.
I havent gotten around to a solution yet but it's likely going to be something like:
move the file to some directory so I know it's one I've decided to send
send it as attachment via ACTION_SEND
at next onResume for my start screen activity, delete files in the "sent" directory that are older than some time frame that's reasonably long enough for the send to actually have happened
Choosing an appropriate time frame might be tricky since it's probably the case that gmail (or other ACTION_SEND providers) dont actually read the file until it has a network connection. I'm thinking 24 hours should be reasonable and in my case I'm dealing with diagnostic logs so there's no real harm in deleting one too soon if the user has been off network for a long period of time.
If the content of your file is text and it's not obscenely large a simpler approach may be to read the contents of the file and use Intent.putExtra(android.content.Intent.EXTRA_TEXT, yourText) to inline it into the body of the message.
What I did is the following.
I used the:
myfile.deleteOnExit();
However, as D.R. mentioned in the comment below correct answer, this does not guarantee the file deletion. This is why I am also deleting the file after the Shared Activity returns. I delete the file if file exists. Because the app crashed sometimes, I put it inside try{} and it works.
I do not know why it does not work for you, but for me it works at least for Gmail attachement, TextSecure, Hangouts.
In class delcaration:
static File file;
In method that calles the Intent:
Intent share = new Intent(Intent.ACTION_SEND);
share.setType("image/png");
// Compress the bitmap to PNG
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, bytes);
// Temporarily store the image to Flash
File sdCard = Environment.getExternalStorageDirectory();
File dir = new File (sdCard.getAbsolutePath() + "/FolderName");
dir.mkdirs();
// This file is static.
file = new File(dir, "FileName.png");
try {
file.createNewFile();
FileOutputStream fo = new FileOutputStream(file);
fo.write(bytes.toByteArray());
fo.flush();
fo.close();
} catch (IOException e) {
e.printStackTrace();
}
// Share compressed image
share.putExtra(Intent.EXTRA_STREAM, Uri.parse("file:///"+file.getPath()));
/** START ACTIVITY **/
startActivityForResult(Intent.createChooser(share,"Share Image"),1);
// Delete Temporary file
file.deleteOnExit(); // sometimes works
In an extra method:
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// Because app crashes sometimes without the try->catch
try {
// if file exists in memory
if (file.exists()) {
file.delete();
}
} catch (Exception e) {
Log.d(LOG,"Some error happened?");
}
}
This is my working solution. Before startActivity() execute this:
FileObserver fo = new FileObserver(file,FileObserver.CLOSE_NOWRITE) {
#Override
public void onEvent(int event, #Nullable String path) {
if(event==FileObserver.CLOSE_NOWRITE) {
file.delete();
this.stopWatching();
// HERE: remove the saved reference to 'fo'
}
}
};
fo.startWatching();
// HERE: save a reference to 'fo'
Important: You should save the reference to 'fo' so that the FileObserver is not garbage-collected.
I have managed to get it to work with:
File tbd = new File(sharePath);
tbd.deleteOnExit();
This seems to delete the file when the activity closes.
Another potential answer would be to create a new thread when your app resumes, immediately mark the current time, sleep the thread for however long you feel is reasonable for the file to be sent, and when the thread resumes, only delete files created before the previously marked time. This will give you the ability to only delete what was in the storage location at the time your app was resumed, but also give time to gmail to get the email out. Code snippet: (I'm using C#/Xamarin, but you should get the idea)
public static void ClearTempFiles()
{
Task.Run(() =>
{
try
{
DateTime threadStartTime = DateTime.UtcNow;
await Task.Delay(TimeSpan.FromMinutes(DeletionDelayMinutes));
DirectoryInfo tempFileDir = new DirectoryInfo(TempFilePath);
FileInfo[] tempFiles = tempFileDir.GetFiles();
foreach (FileInfo tempFile in tempFiles)
{
if (tempFile.CreationTimeUtc < threadStartTime)
{
File.Delete(tempFile.FullName);
}
}
}
catch { }
});
}
#Martijn Pieters This answer is a different solution that handles multiple questions. If anything, the other questions that I posted on, should be marked as duplicates because they are the same question. I posted on each of them to ensure that whoever has this problem, can find the solution.