Bishal Imtiaz
Bishal Imtiaz

Reputation: 489

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

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.

Upvotes: 34

Views: 53031

Answers (7)

test davoodi
test davoodi

Reputation: 1

for destination, you can use it :

 .setDestinationInExternalPublicDir(
                     Environment.DIRECTORY_DOWNLOADS,
                     "/NameOfFolder/$fileName"
                 )

Upvotes: 0

I_4m_Z3r0
I_4m_Z3r0

Reputation: 1080

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

Upvotes: 15

Muhammad Asad
Muhammad Asad

Reputation: 668

sure this tag in your manifest file:

    android:requestLegacyExternalStorage="true"

in Android 10,11 required to use this tag

Upvotes: -4

thilina Kj
thilina Kj

Reputation: 1413

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)

Upvotes: 21

Bishal Imtiaz
Bishal Imtiaz

Reputation: 489

private void DownloadImage(String url){
    String filename=url.substring(url.lastIndexOf("/")+1);
    File file=new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES ).getPath()+"/UnifiedClothes/" + filename);
    Log.d("Environment", "Environment extraData=" + file.getPath());

    DownloadManager.Request request=new DownloadManager.Request(Uri.parse(url))
            .setTitle(filename)
            .setDescription("Downloading")
            .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
            .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
            .setDestinationUri(Uri.fromFile(file))
            .setAllowedOverMetered(true)
            .setAllowedOverRoaming(true);
    downloadManager= (DownloadManager) context.getSystemService(DOWNLOAD_SERVICE);
    referenceID = downloadManager.enqueue(request);

}

Upvotes: 3

Vajani Kishan
Vajani Kishan

Reputation: 313

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 29
buildToolsVersion "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.saveimage

import android.app.DownloadManager
import android.content.Context
import android.net.Uri
import android.os.Bundle
import android.os.Environment
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import java.io.File


class 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)

    }
}

Upvotes: 2

alexsanderfr
alexsanderfr

Reputation: 56

I believe the problem is in the line you mentioned. The concatenation you're setting up is changing the directory of the file to one that is not standard.

request.setDestinationInExternalPublicDir(getExternalFilesDir(Environment.DIRECTORY_PICTURES) + "/NewFile","sample2.jpg")

The error shows that. See that the line tells you which directory it is trying to save to.

/storage/emulated/0/Android/data/com.blz.prisoner.downloadmanager/files/Pictures/NewFile

So when you do that, it will try to save as:

/storage/emulated/0/Android/data/com.blz.prisoner.downloadmanager/files/Pictures/NewFile/sample2.jpg

A solution to this would be to save the file to the standard directory. To do so you would only need to remove the concatenation.

request.setDestinationInExternalPublicDir(getExternalFilesDir(Environment.DIRECTORY_PICTURES),"sample2.jpg")

Then it will try to save to the standard directory

/storage/emulated/0/Android/data/com.blz.prisoner.downloadmanager/files/Pictures/sample2.jpg

Upvotes: 2

Related Questions