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

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


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());        }    }

Important:
from 05/07/2021 if your app is in the Google Play Store, theorically, you must targetSdk=30 and also you must use Scoped Storage only to access your files (so use only the app-specific directory for your app).This means you need to use:

context.getFilesDir();


To use Download manager to download files in Android Q and below :

If you are targeting Android Q(29) no need to opt-out of scoped storage.(android:requestLegacyExternalStorage="true" no need)

Manifest file

<uses-permission    android:name="android.permission.WRITE_EXTERNAL_STORAGE"    android:maxSdkVersion="28" /><uses-permission android:name="android.permission.INTERNET"/><uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER" />

Code :

 private fun onDownload() {    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {        downloadFile("xxx.jpg", "File Desc", "url")    }else{        val result = requestRuntimePermission(            this,            Manifest.permission.WRITE_EXTERNAL_STORAGE        )        result.success {             downloadFile("xxx.jpg", "File Desc", "url")        }    }}private fun downloadFile(fileName : String, desc :String, url : String){    // fileName -> fileName with extension    val request = DownloadManager.Request(Uri.parse(url))        .setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE)        .setTitle(fileName)        .setDescription(desc)        .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)        .setAllowedOverMetered(true)        .setAllowedOverRoaming(false)        .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS,fileName)    val downloadManager= getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager    val downloadID = downloadManager.enqueue(request)}

To Store file in External App-Specific Directory

.setDestinationInExternalFilesDir(context, Environment.DIRECTORY_MUSIC,fileName)

To Store file in External Public Directory

.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS,fileName)


As the problem we are facing and also as per the documentation I conclude that from API level 29 they are not allowing us to create any non standard directory(User Defined) directly but i found one working way for solve that issue.

Here is the my manifest file

<uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><application    android:allowBackup="true"    android:icon="@mipmap/ic_launcher"    android:label="@string/app_name"    android:requestLegacyExternalStorage="true"    android:roundIcon="@mipmap/ic_launcher_round"    android:supportsRtl="true"    android:theme="@style/AppTheme">    <activity android:name=".MainActivity">        <intent-filter>            <action android:name="android.intent.action.MAIN" />            <category android:name="android.intent.category.LAUNCHER" />        </intent-filter>    </activity></application>

Here is my build.gradle file just for ensuring you i am using target sdk 29

apply plugin: 'com.android.application'apply plugin: 'kotlin-android'apply plugin: 'kotlin-android-extensions'android {compileSdkVersion 29buildToolsVersion "29.0.3"defaultConfig {    applicationId "com.example.saveimage"    minSdkVersion 16    targetSdkVersion 29    versionCode 1    versionName "1.0"    testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"}buildTypes {    release {        minifyEnabled false        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'    }  }}dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"implementation 'androidx.appcompat:appcompat:1.1.0'implementation 'androidx.core:core-ktx:1.2.0'implementation 'androidx.constraintlayout:constraintlayout:1.1.3'testImplementation 'junit:junit:4.12'androidTestImplementation 'androidx.test.ext:junit:1.1.1'androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'}

And Here is the MainActivity.kt

package com.example.saveimageimport android.app.DownloadManagerimport android.content.Contextimport android.net.Uriimport android.os.Bundleimport android.os.Environmentimport androidx.appcompat.app.AppCompatActivityimport kotlinx.android.synthetic.main.activity_main.*import java.io.Fileclass MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {    super.onCreate(savedInstanceState)    setContentView(R.layout.activity_main)    btnSave.setOnClickListener {        downloadFile("https://homepages.cae.wisc.edu/~ece533/images/airplane.png") //Your URL    }}fun downloadFile(uRl: String) {    val direct = File(getExternalFilesDir(null), "/KrishanImages")    if (!direct.exists()) {        direct.mkdirs()    }    val mgr = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager    val downloadUri = Uri.parse(uRl)    val request = DownloadManager.Request(        downloadUri    )    request.setAllowedNetworkTypes(        DownloadManager.Request.NETWORK_WIFI or          DownloadManager.Request.NETWORK_MOBILE    )        .setAllowedOverRoaming(false).setTitle("Krishan Demo") //Download Manager Title        .setDescription("Downloading...") //Download Manager description        .setDestinationInExternalPublicDir(            Environment.DIRECTORY_PICTURES,            "/KrishanImages/sample2.jpg"  //Your User define(Non Standard Directory name)/File Name        )    mgr.enqueue(request)    }}