I'm trying to download a file stored in Google Drive using android DownloadManager.
I get the sign in token from Google like following:
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(AppConfig.getInstance().get("google_client_id"))
.requestProfile()
.requestEmail()
.build();
I receive a notification with google drive file url and i pass it to the DownloadManager, passing to it the token:
String cookie = CookieManager.getInstance().getCookie(d.getURL());
request.addRequestHeader("Cookie",cookie);
request.addRequestHeader("Authorization", "OAuth " + profile.getToken());
//d is the document object, that contains url, file name, etcc
//Profile is a simple object class that hold the user data taken from sign Google, like token, name, email, etcc
Using a simple Broadcast Receiver to manage the download result (ACTION_DOWNLOAD_COMPLETE).
The download is done successfully, but the file is corrupted.
If i try to open it, the device (and pc) gives me a format error.
The file contains a HTML code of a Google page that says that there war an error, no more.
(The account that i'm using is enabled to read and dwonload the document form this specific drive storage)
Is this the correct way to download a Google Drive file using DownloadManager? Is it possible to do that?
Try whether this helps...
As in #saddamkamal 's answer, use the Google Drive download URL.
AsyncTask.execute(new Runnable() {
#Override
public void run() {
DownloadManager downloadmanager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
Uri uri = Uri.parse("https://drive.google.com/uc?id=<FILE_ID>&export=download");
DownloadManager.Request request = new DownloadManager.Request(uri);
request.setTitle("My File");
request.setDescription("Downloading");
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "file.extension");
downloadmanager.enqueue(request);
}
});
You can also downlaod file form google via this method
Refer to this Url
https://drive.google.com/uc?id=<FILE_ID>&export=download
Replace <FILE_ID> with your shareable file ID.
Further you can take help from this solution Download a file with Android, and showing the progress in a ProgressDialog
You can use the doInBackground function in it to solve your query.
Since file is downloaded.
Check the size of file in Google drive and your android.
Then make sure your file extension is correct.
Because file extension may not be present and android will treat it as binary file.
Now you have file extension in android. Install proper application to open it.
This is updated code
public class MainActivity extends AppCompatActivity {
private Button btn_download;
private long downloadID;
// using broadcast method
private BroadcastReceiver onDownloadComplete = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
//Fetching the download id received with the broadcast
long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
//Checking if the received broadcast is for our enqueued download by matching download id
if (downloadID == id) {
Toast.makeText(MainActivity.this, "Download Completed", Toast.LENGTH_SHORT).show();
}
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_download = findViewById(R.id.download_btn);
// using broadcast method
registerReceiver(onDownloadComplete,new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
beginDownload();
}
});
}
#Override
public void onDestroy() {
super.onDestroy();
// using broadcast method
unregisterReceiver(onDownloadComplete);
}
private void beginDownload(){
String url = "http://speedtest.ftp.otenet.gr/files/test10Mb.db";
String fileName = url.substring(url.lastIndexOf('/') + 1);
fileName = fileName.substring(0,1).toUpperCase() + fileName.substring(1);
File file = Util.createDocumentFile(fileName, context);
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url))
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN)// Visibility of the download Notification
.setDestinationUri(Uri.fromFile(file))// Uri of the destination file
.setTitle(fileName)// Title of the Download Notification
.setDescription("Downloading")// Description of the Download Notification
.setRequiresCharging(false)// Set if charging is required to begin the download
.setAllowedOverMetered(true)// Set if download is allowed on Mobile network
.setAllowedOverRoaming(true);// Set if download is allowed on roaming network
DownloadManager downloadManager= (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
downloadID = downloadManager.enqueue(request);// enqueue puts the download request in the queue.
// using query method
boolean finishDownload = false;
int progress;
while (!finishDownload) {
Cursor cursor = downloadManager.query(new DownloadManager.Query().setFilterById(downloadID));
if (cursor.moveToFirst()) {
int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
switch (status) {
case DownloadManager.STATUS_FAILED: {
finishDownload = true;
break;
}
case DownloadManager.STATUS_PAUSED:
break;
case DownloadManager.STATUS_PENDING:
break;
case DownloadManager.STATUS_RUNNING: {
final long total = cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
if (total >= 0) {
final long downloaded = cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
progress = (int) ((downloaded * 100L) / total);
// if you use downloadmanger in async task, here you can use like this to display progress.
// Don't forget to do the division in long to get more digits rather than double.
// publishProgress((int) ((downloaded * 100L) / total));
}
break;
}
case DownloadManager.STATUS_SUCCESSFUL: {
progress = 100;
// if you use aysnc task
// publishProgress(100);
finishDownload = true;
Toast.makeText(MainActivity.this, "Download Completed", Toast.LENGTH_SHORT).show();
break;
}
}
}
}
}
}
Related
I'm currently creating an app that needs to download a couple of videos then save the local path of it on a SQLite database.
At first, I wanted to get the URL of the video I downloaded but I can't seem to find anything that discusses about it. I tried to get COLUMN_MEDIAPROVIDER_URI and COLUMN_URI from the intent passed on the BroadcastReceiver for DownloadManager.ACTION_DOWNLOAD_COMPLETE but they return null.
Then I found about EXTRA_DOWNLOAD_ID. But if I use that, I still need to use something like a new HashMap that got the EXTRA_DOWNLOAD_ID of my download and the id of the video on my SQLite database for checking which is which.
I'm fine with that but I want to know if there's an easier way to do the thing I want.
I did this using OkHttp, as follows:
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(YOUR_URL)
.build();
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
// ERROR MESSAGE
}
#Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
response.body().byteStream(); // byteStream with your result.
}
}
});
Another thing, maybe would be better if you store the videos on memory and just the address in your SQLite.
Using the code below from the SO question here
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
// get the DownloadManager instance
DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Query q = new DownloadManager.Query();
Cursor c = manager.query(q);
if(c.moveToFirst()) {
do {
String name = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
Log.i("DOWNLOAD LISTENER", "file name: " + name);
} while (c.moveToNext());
} else {
Log.i("DOWNLOAD LISTENER", "empty cursor :(");
}
c.close();
}
}
and saving the download id on my ArrayList I was able to make a simpler way to check which download is finished.
I modified it to look like this for my use case.
Cursor c = dlMgr.query(new DownloadManager.Query());
boolean found = false;
if(c.moveToFirst()) {
do {
String dlFilePath = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
int dlId = Integer.parseInt( c.getString(c.getColumnIndex(DownloadManager.COLUMN_ID)) );
for(int x = 0; x < vidArrLst.size(); x++){
VideoAd va = vidArrLst.get(x);
if(va.getDownloadId() == dlId){
dbHelper.updateLocalPath(va.getVideoId(), dlFilePath);
va.setLocalPath(dlFilePath);
found = true;
break;
}
}
} while (c.moveToNext() && !found);
} else {
Log.d(TAG, "empty cursor :(");
}
UPDATE:
Sometimes this method will show that 2 downloads finished with the same file name which results to a video item to not have a local path. What I did is check if the local path is empty, download id is greater than 0, and if the download id is still downloading before playing a video so I can redownload a video and fix the gap and play the local file the next time the video needs to be played.
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...
I used Download Manager class inside my activity to perform downloads; it works fine and my next task is to show the same progress percentage inside my activity. I am not sure how to do it.
My code so far
public class DownloadSampleBook extends Activity{
private long enqueue;
private DownloadManager dm;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sample_download);
BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
long downloadId = intent.getLongExtra(
DownloadManager.EXTRA_DOWNLOAD_ID, 0);
Query query = new Query();
query.setFilterById(enqueue);
Cursor c = dm.query(query);
if (c.moveToFirst()) {
int columnIndex = c
.getColumnIndex(DownloadManager.COLUMN_STATUS);
if (DownloadManager.STATUS_SUCCESSFUL == c
.getInt(columnIndex)) {
view.setImageURI(Uri.parse(uriString));
}
}
}
}
};
registerReceiver(receiver, new IntentFilter(
DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}
public void onClick(View view) {
dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
Request request = new Request(
Uri.parse("http://abc.com/a.png"));
enqueue = dm.enqueue(request);
}
public void showDownload(View view) {
Intent i = new Intent();
i.setAction(DownloadManager.ACTION_VIEW_DOWNLOADS);
startActivity(i);
}
}
Is there any method that give the progress download percentage?
If you are looking for a decent way to determine when to query the DownloadManager for progress updates, consider registering a ContentObserver for the uri content://downloads/my_downloads
Example:
DownloadManager manager = (DownloadManager) getSystemService( Context.DOWNLOAD_SERVICE );
manager.enqueue( myRequest );
Uri myDownloads = Uri.parse( "content://downloads/my_downloads" );
getContentResolver().registerContentObserver( myDownloads, true, new DownloadObserver() );
...
public static class DownloadObserver extends ContentObserver {
#Override
public void onChange( boolean selfChange, Uri uri ) {
Log.d( "DownloadObserver", "Download " + uri + " updated" );
}
This yields the following output as each chunk of the long running download is received
D/DownloadObserver(15584): Download content://downloads/my_downloads/437 updated
D/DownloadObserver(15584): Download content://downloads/my_downloads/437 updated
D/DownloadObserver(15584): Download content://downloads/my_downloads/437 updated
D/DownloadObserver(15584): Download content://downloads/my_downloads/437 updated
where '437' is the ID of your download.
Note that this follows the content URI defined in the class android.provider.Downloads which appears to be hidden in the framework and may not work consistently on all devices. (https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/provider/Downloads.java#89)
You can query the number of bytes downloaded so far, and the total number of bytes that need to be downloaded, using the query method, in much the same way as you have queried the status in your example code. Once you have those values, it's fairly easy to calculate the progress as a percentage.
There doesn't appear to be any way for you to be notified when new data is received, so it would be up to you to poll the download manager at some regular interval to determine the current status of any download that you want to monitor.
Query query = new Query();
query.setfilterById(downloadId);
Cursor c = dm.query(query);
if (c.moveToFirst()) {
int sizeIndex = c.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES);
int downloadedIndex = c.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR);
long size = c.getInt(sizeIndex);
long downloaded = c.getInt(downloadedIndex);
double progress = 0.0;
if (size != -1) progress = downloaded*100.0/size;
// At this point you have the progress as a percentage.
}
Note that the total size will initially be -1 and will only be filled in once the download starts. So in the sample code above I've checked for -1 and set the progress to 0 if the size is not yet set.
However, you may find in some cases that the total size is never returned (for example, in an HTTP chunked transfer, there will be no Content-Length header from which the size can be determined). If you need to support that kind of server, you should probably provide some kind of indication to the user that the download is progressing and not just a progress bar that is stuck at zero.
I had a requirement of tracking download of multiple files. After a lot of thinking and experimenting, I came up with the following code:
private void startDownloadThread(final List<DownloadFile> list) {
// Initializing the broadcast receiver ...
mBroadCastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
mFinishedFilesFromNotif.add(intent.getExtras()
.getLong(DownloadManager.EXTRA_DOWNLOAD_ID));
}
};
IntentFilter intentFilter = new IntentFilter(
"android.intent.action.DOWNLOAD_COMPLETE");
DownloadProgressUIFragment.this.getActivity().registerReceiver(mBroadCastReceiver,
intentFilter);
// initializing the download manager instance ....
mDownloadManager = (DownloadManager) getActivity()
.getSystemService(Context.DOWNLOAD_SERVICE);
// adding files to the download manager list ...
for(DownloadFile f: list) {
mDownloadIds.add(FileUtils.addFileForDownloadInBkg(getApplicationContext(),
f.getUrl(),
f.getPath()));
}
// starting the thread to track the progress of the download ..
mProgressThread = new Thread(new Runnable() {
#Override
public void run() {
// Preparing the query for the download manager ...
DownloadManager.Query q = new DownloadManager.Query();
long[] ids = new long[mDownloadIds.size()];
final List<Long> idsArrList= new ArrayList<>();
int i = 0;
for (Long id: mDownloadIds) {
ids[i++] = id;
idsArrList.add(id);
}
q.setFilterById(ids);
// getting the total size of the data ...
Cursor c;
while(true) {
// check if the downloads are already completed ...
// Here I have created a set of download ids from download manager to keep
// track of all the files that are dowloaded, which I populate by creating
//
if(mFinishedFilesFromNotif.containsAll(idsArrList)) {
isDownloadSuccess = true;
// TODO - Take appropriate action. Download is finished successfully
return;
}
// start iterating and noting progress ..
c = mDownloadManager.query(q);
if(c != null) {
int filesDownloaded = 0;
float fileFracs = 0f; // this stores the fraction of all the files in
// download
final int columnTotalSize = c.getColumnIndex
(DownloadManager.COLUMN_TOTAL_SIZE_BYTES);
final int columnStatus = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
//final int columnId = c.getColumnIndex(DownloadManager.COLUMN_ID);
final int columnDwnldSoFar =
c.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR);
while (c.moveToNext()) {
// checking the progress ..
if(c.getInt(columnStatus) == DownloadManager.STATUS_SUCCESSFUL) {
filesDownloaded++;
}
// If the file is partially downloaded, take its fraction ..
else if(c.getInt(columnTotalSize) > 0) {
fileFracs += ((c.getInt(columnDwnldSoFar) * 1.0f) /
c.getInt(columnTotalSize));
} else if(c.getInt(columnStatus) == DownloadManager.STATUS_FAILED) {
// TODO - Take appropriate action. Error in downloading one of the
// files.
return;
}
}
c.close();
// calculate the progress to show ...
float progress = (filesDownloaded + fileFracs)/ids.length;
// setting the progress text and bar...
final int percentage = Math.round(progress * 100.0f);
final String txt = "Loading ... " + percentage + "%";
// Show the progress appropriately ...
}
}
}
});
mProgressThread.start();
}
And the function to enqueue to files are:
public static long addFileForDownloadInBkg(Context context, String url, String savePath) {
Uri uri = Uri.parse(url);
DownloadManager.Request request = new DownloadManager.Request(uri);
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
request.setDestinationUri(Uri.fromFile(new File(savePath)));
final DownloadManager m = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
return m.enqueue(request);
}
Basically, I receive a notification individually for each of the files whose download has been finished and then add them to a set which is basically the set which helps me decide if all the downloads have been finished or not. I track the the progress based on the number of files and the fraction of each being complete. I hope this helps.
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”.
I have the following problem: Whenever I download a file with the DownloadManager it is downloaded twice (saved in the fashion "filename.extension" and "filename-1.extension"). Here is my code:
public void download() {
Request request = new Request(Uri.parse(_wrapper.getURL()));
request.setTitle(getFileName(_wrapper.getURL()));
request.setVisibleInDownloadsUi(false);
request.setDestinationInExternalFilesDir(_context, null, "/" + getFileName(_wrapper.getURL()));
_downloadID = _downloadManager.enqueue(request);
}
public BroadcastReceiver getDownloadFinishedBroadcastReceiver() {
BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context pContext, Intent pIntent) {
String action = pIntent.getAction();
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
Query query = new Query();
query.setFilterById(_downloadID);
Cursor cursor = _downloadManager.query(query);
if (cursor.moveToFirst()) {
File file = new File(ScruloidConstants.APPLICATION_DIRECTORY);
int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
if (status == DownloadManager.STATUS_SUCCESSFUL) {
String path = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
_wrapper.setFilePath(path);
_wrapper.setLastDownloaded(new Date());
if (_listener != null) {
_listener.onDownloadProjectTaskFinished(new TaskResult<ProjectWrapper>(_wrapper));
}
}
else if (status == DownloadManager.STATUS_FAILED) {
int reason = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_REASON));
DownloadFailedException ex = new DownloadFailedException(reason);
if (_listener != null) {
_listener.onDownloadProjectTaskFinished(new TaskResult<ProjectWrapper>(ex));
}
}
}
}
}
};
return receiver;
}
The ProjectWrapper _wrapper is just a simple Class that holds data, no logic is done there. The _listener just displays on the callback method a little Toast message. I debugged my app to make shure the download() Method is invoked only once. I hope you can help me find the error.
Unfortunately, DownloadManager is buggy and doesn't work correctly on all devices. Your problem is reported here: https://code.google.com/p/android/issues/detail?id=18462
I've got the same error on mobile devices with API 21, I've made a workaround to verify before creating a request, if the file name used to set de request destination was equal one of the last files already downloaded, or if its a substring of any previews downloaded
if (!mLastMediaDownloadedId.any { it.contains(outputFile.name) }) {
mLastMediaDownloadedId.add(outputFile.name)
val url =
AppConstants.AWS_MEDIA_BUCKET_PATH + scoutObjectType.endPoint() + "$scoutObjectId.png"
val request = DownloadManager.Request(Uri.parse(url))
.setDestinationUri(Uri.fromFile(outputFile))
.setTitle("Downlading media")
.setDescription("Downloading image medias")
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
.setAllowedOverRoaming(true)
.setAllowedOverMetered(true)
val downloadId = it.enqueue(request)
downloadIds.add(downloadId)
downloadId
}
and where "outputFile" is the file name itself to be downloaded, in your case this should be "filename.extension"
PS: Sorry for the Kotlin code, but it should be a good representation for the workaround itself