How to Download File Using DownloadManager in API 29 or Android Q?

Bishal Imtiaz picture Bishal Imtiaz · Sep 24, 2019 · Viewed 15.7k times · Source

As I am new in Android Development, I am trying to simple App using DownloadManager.

Here is the code

public class MainActivity extends AppCompatActivity implements ActivityCompat.OnRequestPermissionsResultCallback{

Button btn;

private long referenceID;
private DownloadManager downloadManager;
private static final int PERMISSION_REQUEST_CODE = 1;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    btn = findViewById(R.id.btn);

    btn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {



            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){


                if (checkPermission())
                {

                    /*** If Storage Permission Is Given, Check External storage is available for read and write***/

                    Uri image_uri = Uri.parse("https://unifiedclothes.com/Unifiedclothes/App_Gallery/thumb_8_121432471036-1432471036-SC-505.jpg");

                    referenceID = DownloadImage(image_uri);




                } else {

                    requestPermission();
                }

            }

            else{
                Toast.makeText(MainActivity.this,"Permission Is Granted..",Toast.LENGTH_SHORT).show();

            }
        }
    });

    registerReceiver(receiver,new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}

private BroadcastReceiver receiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {

        String action = intent.getAction();

        if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)){



            DownloadManager.Query ImageDownloadQuery = new DownloadManager.Query();
            //set the query filter to our previously Enqueued download
            ImageDownloadQuery.setFilterById(referenceID);

            //Query the download manager about downloads that have been requested.
            Cursor cursor = downloadManager.query(ImageDownloadQuery);

            if(cursor.moveToFirst()){

                Toast.makeText(MainActivity.this,DownloadStatus(cursor),Toast.LENGTH_SHORT).show();
            }



        }

    }
};

private String DownloadStatus(Cursor cursor){

    //column for download  status
    int columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
    int status = cursor.getInt(columnIndex);
    //column for reason code if the download failed or paused
    int columnReason = cursor.getColumnIndex(DownloadManager.COLUMN_REASON);
    int reason = cursor.getInt(columnReason);



    String statusText = "";
    String reasonText = "";

    switch(status){
        case DownloadManager.STATUS_FAILED:
            statusText = "STATUS_FAILED";
            switch(reason){
                case DownloadManager.ERROR_CANNOT_RESUME:
                    reasonText = "ERROR_CANNOT_RESUME";
                    break;
                case DownloadManager.ERROR_DEVICE_NOT_FOUND:
                    reasonText = "ERROR_DEVICE_NOT_FOUND";
                    break;
                case DownloadManager.ERROR_FILE_ALREADY_EXISTS:
                    reasonText = "ERROR_FILE_ALREADY_EXISTS";
                    break;
                case DownloadManager.ERROR_FILE_ERROR:
                    reasonText = "ERROR_FILE_ERROR";
                    break;
                case DownloadManager.ERROR_HTTP_DATA_ERROR:
                    reasonText = "ERROR_HTTP_DATA_ERROR";
                    break;
                case DownloadManager.ERROR_INSUFFICIENT_SPACE:
                    reasonText = "ERROR_INSUFFICIENT_SPACE";
                    break;
                case DownloadManager.ERROR_TOO_MANY_REDIRECTS:
                    reasonText = "ERROR_TOO_MANY_REDIRECTS";
                    break;
                case DownloadManager.ERROR_UNHANDLED_HTTP_CODE:
                    reasonText = "ERROR_UNHANDLED_HTTP_CODE";
                    break;
                case DownloadManager.ERROR_UNKNOWN:
                    reasonText = "ERROR_UNKNOWN";
                    break;
            }
            break;
        case DownloadManager.STATUS_PAUSED:
            statusText = "STATUS_PAUSED";
            switch(reason){
                case DownloadManager.PAUSED_QUEUED_FOR_WIFI:
                    reasonText = "PAUSED_QUEUED_FOR_WIFI";
                    break;
                case DownloadManager.PAUSED_UNKNOWN:
                    reasonText = "PAUSED_UNKNOWN";
                    break;
                case DownloadManager.PAUSED_WAITING_FOR_NETWORK:
                    reasonText = "PAUSED_WAITING_FOR_NETWORK";
                    break;
                case DownloadManager.PAUSED_WAITING_TO_RETRY:
                    reasonText = "PAUSED_WAITING_TO_RETRY";
                    break;
            }
            break;
        case DownloadManager.STATUS_PENDING:
            statusText = "STATUS_PENDING";
            break;
        case DownloadManager.STATUS_SUCCESSFUL:
            statusText = "Image Saved Successfully";
            //reasonText = "Filename:\n" + filename;
            Toast.makeText(MainActivity.this, "Download Status:" + "\n" + statusText + "\n" + reasonText, Toast.LENGTH_SHORT).show();
            break;
    }

    return statusText + reasonText;


}


private long DownloadImage(Uri uri){

    long downloadReference;

    downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);

    DownloadManager.Request request = new DownloadManager.Request(uri);
    //Setting title of request
    request.setTitle("Image Download");

    //Setting description of request
    request.setDescription("Image download using DownloadManager.");


    request.setDestinationInExternalPublicDir(getExternalFilesDir(Environment.DIRECTORY_PICTURES) + "/NewFile","sample2.jpg");
    //request.allowScanningByMediaScanner();
    request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
    downloadReference = downloadManager.enqueue(request);


    return  downloadReference;
}


private boolean checkPermission() {
    int result = ContextCompat.checkSelfPermission(MainActivity.this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE);
    if (result == PackageManager.PERMISSION_GRANTED) {
        return true;
    } else {
        return false;
    }
}


private void requestPermission() {

    ActivityCompat.requestPermissions(MainActivity.this, new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE);
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);

    if (requestCode == PERMISSION_REQUEST_CODE) {

        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

            Uri image_uri = Uri.parse("https://www.dccomics.com/sites/default/files/Char_GetToKnow_Batman80_5ca54cb83a27a6.53173051.png");

            referenceID = DownloadImage(image_uri);


        }

        else {

            Toast.makeText(MainActivity.this, "Permission Denied... \n You Should Allow External Storage Permission To Download Images.", Toast.LENGTH_LONG).show();
        }
    }
}

}

It works well when i run it on any device below API 29(My testing device was Nexus 5X ,ApI 28 emulator). But when I run it on Nexus 5X ,API 29 The app gets crashed. Here is the Logs:

2019-09-24 20:51:46.354 11322-11344/? E/DatabaseUtils: Writing exception to parcel
java.lang.IllegalStateException: Not one of standard directories: /storage/emulated/0/Android/data/com.blz.prisoner.downloadmanager/files/Pictures/NewFile
    at com.android.providers.downloads.DownloadProvider.call(DownloadProvider.java:651)
    at android.content.ContentProvider.call(ContentProvider.java:2152)
    at android.content.ContentProvider$Transport.call(ContentProvider.java:477)
    at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:277)
    at android.os.Binder.execTransactInternal(Binder.java:1021)
    at android.os.Binder.execTransact(Binder.java:994)

2019-09-24 20:51:46.355 15023-15023/com.blz.prisoner.downloadmanager D/AndroidRuntime: Shutting down VM

--------- beginning of crash
2019-09-24 20:51:46.360 15023-15023/com.blz.prisoner.downloadmanager E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.blz.prisoner.downloadmanager, PID: 15023
    java.lang.IllegalStateException: Not one of standard directories: /storage/emulated/0/Android/data/com.blz.prisoner.downloadmanager/files/Pictures/NewFile
        at android.os.Parcel.createException(Parcel.java:2079)
        at android.os.Parcel.readException(Parcel.java:2039)
        at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:188)
        at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:140)
        at android.content.ContentProviderProxy.call(ContentProviderNative.java:658)
        at android.content.ContentProviderClient.call(ContentProviderClient.java:558)
        at android.content.ContentProviderClient.call(ContentProviderClient.java:546)
        at android.app.DownloadManager$Request.setDestinationInExternalPublicDir(DownloadManager.java:567)
        at com.blz.prisoner.downloadmanager.MainActivity.DownloadImage(MainActivity.java:206)
        at com.blz.prisoner.downloadmanager.MainActivity.access$200(MainActivity.java:29)
        at com.blz.prisoner.downloadmanager.MainActivity$1.onClick(MainActivity.java:60)
        at android.view.View.performClick(View.java:7140)
        at android.view.View.performClickInternal(View.java:7117)
        at android.view.View.access$3500(View.java:801)
        at android.view.View$PerformClick.run(View.java:27351)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7356)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

I think the problem is on line " request.setDestinationInExternalPublicDir(getExternalFilesDir(Environment.DIRECTORY_PICTURES) + "/NewFile","sample2.jpg");" in DownloadImage(Uri uri) function. How to solve the problem??

Another Problem is when I run the app on the device below API 29 it runs well but when I click on notification after completing the download it doesn't open the image on gallery/on the folder it was saved.

Answer

Z3R0 picture Z3R0 · Feb 5, 2020

I solved just by using:

setDestinationInExternalFilesDir(context, relativePath, filename);

Instead of:

setDestinationInExternalPublicDir(relativePath, filename);

My relative path is:

Environment.getExternalStorageDirectory().getPath() + "MyExternalStorageAppPath"

I also have in my manifest:

android:requestLegacyExternalStorage="true"

To use Legacy storage management (Shared Storage) instead of new storage management (Scoped Storage) used from Android 10 and above.

Remember that by using "setDestinationInExternalFilesDir" files will be download to the external memory dedicated to your app, so: "external/Android/data/your_app_name/path_you_used_on_function". If you want to download it to another place you need to move It after you downloaded It by using Input & Output streams. To open the file with another app in Android version 10 or above you must use FileProvider.

If someone need it, this is the code to move (move, not copy. So the original file will be deleted. Remove "source.delete();" if you want to copy the file and not delete the source file) a file from one location to another:

public static boolean moveFile(File source, String destPath){
        if(source.exists()){
            File dest = new File(destPath);
            checkMakeDirs(dest.getParent());
            try (FileInputStream fis = new FileInputStream(source);
                 FileOutputStream fos = new FileOutputStream(dest)){
                if(!dest.exists()){
                    dest.createNewFile();
                }
                writeToOutputStream(fis, fos);
                source.delete();
                return true;
            } catch (IOException ioE){
                Log.e(TAG, ioE.getMessage());
            }
        }
        return false;
    }

private static void writeToOutputStream(InputStream is, OutputStream os) throws IOException {
        byte[] buffer = new byte[1024];
        int length;
        if (is != null) {
            while ((length = is.read(buffer)) > 0x0) {
                os.write(buffer, 0x0, length);
            }
        }
        os.flush();
    }

Usage ("source" is the File you need to move, "path" is the destination):

if(FilesUtils.moveFile(source, path)) {
     // Success Moving File, do what you need with it
}

Broadcast receiver for when the DownloadManager has finished:

private static class DownloadFileReceiver extends BroadcastReceiver {

        private DownloadManager mDownloadManager;
        private String mPath;

        private DownloadFileReceiver(DownloadManager dManager, String path){
            mDownloadManager = dManager;
            mPath = path;
        }

        /** Override BroadcastReceiver Methods **/
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
                Bundle extras = intent.getExtras();
                DownloadManager.Query q = new DownloadManager.Query();
                q.setFilterById(extras.getLong(DownloadManager.EXTRA_DOWNLOAD_ID));
                Cursor c = mDownloadManager.query(q);
                if (c.moveToFirst()) {
                    int status = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
                    if (status == DownloadManager.STATUS_SUCCESSFUL) {
                        String fullPath = null; File source = null;
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                            fullPath = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
                            source = new File(Uri.parse(fullPath).getPath());
                        } else {
                            fullPath = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
                            source = new File(fullPath);
                        }
                    }
                }
                c.close();
            }
            Objects.requireNonNull(context).unregisterReceiver(this);
        }
    }

Register it to the DownloadManager instance:

context.registerReceiver(new DownloadFileReceiver(downloadManager, path),
                new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));

checkMakeDirs (this check if the dir exists or if it can make it successfully) and makeDirs (just make it without checking) code:

public static boolean checkMakeDirs(String dirPath){
        try {
            File dir = new File(dirPath);
            return dir.exists() || dir.mkdirs();
        } catch (Exception e) {
            Log.e(TAG, e.getMessage());
        }
        return false;
    }

    public static void makeDirs(String dirPath){
        try {
            File dir = new File(dirPath);
            if(!dir.exists()){
                dir.mkdirs();
            }
        } catch (Exception e){
            Log.e(TAG, e.getMessage());
        }
    }