I am targeting Android 10 / api 29 and the DownloadManager works fine on version 10 or lower. It also worked on the R preview (I think it was preview 2). However when I test in the emulator using the new api 30 (revision 6) it will not work.
When running on an emulator with api 30 and calling setDestinationInExternalFilesDir it gives an exception on that very same line:
java.lang.IllegalStateException: Failed to get external storage files directory
at android.app.DownloadManager$Request.setDestinationInExternalFilesDir
The code is like this:
request.setDestinationInExternalFilesDir(context, Environment.DIRECTORY_DOWNLOADS + "/foo/", "bar.zip");
If i instead call setDestinationUri there is an exception when calling enqueue with the request:
request.setDestinationUri(Uri.fromFile(new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS) + "/foo/", "bar.zip")));
Exception:
java.lang.SecurityException: Unsupported path /null/foo/bar.zip
The line of the exception is like this:
return downloadManager.enqueue(request);
Both setDestinationInExternalFilesDir and setDestinationUri works when running on api 29.
Edit: I also tried targeting api 30 and it has the same problem.
Also, if I try to call this again after an exception the exception wont be thrown however DownloadManager will be unable to download the file and return DownloadManager.STATUS_PAUSED when querying it.
Edit: Here is a full example of this not working on Android 11 but working on Android 10. I am not using android:requestLegacyExternalStorage="true" and it is still working on Android 10. Also in the documentation they state that when using setDestinationUri it will be saved to app specific directory.
import android.app.DownloadManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.os.Environment;
import android.os.SystemClock;
import android.util.Log;
import android.view.View;
import java.io.File;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
DownloadManager.Request request = new DownloadManager.Request(Uri.parse("https://en.wikipedia.org/wiki/Main_Page#/media/File:Tukwila_Int'l_Blvd_station_with_northbound_Link_train_(2009).jpg"));
request.setDescription("Downloading files");
request.setTitle("Test dm");
request.setVisibleInDownloadsUi(false);
request.setDestinationInExternalFilesDir(MainActivity.this, Environment.DIRECTORY_DOWNLOADS, "testimage.jpg");
//request.setDestinationUri(Uri.fromFile(new File(MainActivity.this.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "testimage.jpg")));
DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
long id = downloadManager.enqueue(request);
for (int x = getProgress(id); x < 100; x = getProgress(id)) {
Log.d("getProgress", x + "");
if (x == -1) {
break;
}
SystemClock.sleep(100);
if (getStatus(id) == 0) {
break;
}
}
}
});
}
private int getProgress(long id) {
DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
if (downloadManager != null) {
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(id);
Cursor cursor = downloadManager.query(query);
if (cursor != null && cursor.moveToFirst()) {
int sizeIndex = cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES);
int downloadedIndex = cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR);
long size = cursor.getInt(sizeIndex);
long downloaded = cursor.getInt(downloadedIndex);
Log.d(TAG, "Download status : " + cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)));
if (cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)) == DownloadManager.STATUS_PENDING
|| cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)) == DownloadManager.STATUS_PAUSED
|| cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)) == DownloadManager.STATUS_RUNNING) {
return (int) (downloaded * 100 / size);
} else if (cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)) == DownloadManager.STATUS_SUCCESSFUL) {
return 100;
} else if (cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)) == DownloadManager.STATUS_FAILED) {
Log.e(TAG, "Download failed, Reason : " + cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_REASON)));
return -1;
}
}
if (cursor != null) {
cursor.close();
}
}
return 0;
}
private int getStatus(long id) {
DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
int status = 0;
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(id);
Cursor c = downloadManager.query(query);
if (c != null) {
if (c.moveToFirst()) {
status = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
}
c.close();
}
return status;
}
}
There is also this warning before the exception is thrown:
W/ContextImpl: Failed to ensure /storage/emulated/0/Android/data/...
Your code fails on the emulator but works on a real Pixel 3a with the current beta (RPB2.2000611.009), the file is actually downloaded.
It seems to be this issue.
Strangely enough the suggested workaround (calling getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
manually) does not always work. I had to uninstall the application first.