Reputation: 489
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
Reputation: 1
for destination, you can use it :
.setDestinationInExternalPublicDir(
Environment.DIRECTORY_DOWNLOADS,
"/NameOfFolder/$fileName"
)
Upvotes: 0
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
Reputation: 668
sure this tag in your manifest file:
android:requestLegacyExternalStorage="true"
in Android 10,11 required to use this tag
Upvotes: -4
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
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
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
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