How to set the Download manager time out and enable or disable the Download manager after 2 minutes.
fun downloadFile(url: String) {
val downloadManager = this.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val downloadUri = Uri.parse(url)
val request = DownloadManager.Request(downloadUri).apply {
try {
setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE)
.setAllowedOverRoaming(true)
.setTitle(url.substring(url.lastIndexOf("/") + 1))
// .setDescription("abc")
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
.setDestinationInExternalPublicDir(
Environment.DIRECTORY_DOWNLOADS,
url.substring(url.lastIndexOf("/") + 1)
)
} catch (e: Exception) {
e.printStackTrace()
}
}
//TODO to get the Downloading status
val downloadId = downloadManager.enqueue(request)
val query = DownloadManager.Query().setFilterById(downloadId)
}
In above code how to handle the timeout with download manager.
You can schedule a new thread to run after x milliseconds using Handler.postDelayed(); In that scheduled thread you use would use DownloadManager.query to access the Cursor object which exposes the download status. If the download status indicates the download isn't completed successfully, you can cancel it using DownloadManager.remove()
I tested that this works:
private fun tryDownloadFileButCancelIfTimeout(url: String, millisecondsToTimeoutAfter : Long) {
val downloadManager = this.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val downloadUri = Uri.parse(url)
val request = DownloadManager.Request(downloadUri).apply {
try {
setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE)
.setAllowedOverRoaming(true)
.setTitle(url.substring(url.lastIndexOf("/") + 1))
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
.setDestinationInExternalPublicDir(
Environment.DIRECTORY_DOWNLOADS,
url.substring(url.lastIndexOf("/") + 1)
)
} catch (e: Exception) {
e.printStackTrace()
}
}
val downloadId = downloadManager.enqueue(request)
// Schedule a new thread that will cancel the download after millisecondsToTimeoutAfter milliseconds
Handler(Looper.getMainLooper()).postDelayed({
val downloadQuery = DownloadManager.Query().setFilterById(downloadId)
val cursor = downloadManager.query(downloadQuery)
val downloadStatusColumn = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)
cursor.moveToPosition(0)
val downloadStatus = cursor.getInt(downloadStatusColumn)
if (downloadStatus != DownloadManager.STATUS_SUCCESSFUL) {
downloadManager.remove(downloadId)
}
}, millisecondsToTimeoutAfter)
}
Posting the code I used to test (can import to Android Studio if you like, simple app with a single download button that starts the download and cancels in five seconds):
https://github.com/hishamhijjawi/DownloadCancelDemoStackOverflow
Related
I have an implementation of DownloadManager that works correctly on most devices I've tested it. I recently started testing on a Samsung Galaxy S10 (Android 9), and I noticed a completely different behavior. the queued download takes up to 10 min to even start, I can see this because my download's request visibility is VISIBILITY_VISIBLE_NOTIFY_COMPLETED so it shows up as a notification after several min of the download request being queued.
When the download completes (either failing or succeeding) I also noticed that I don't get a call to my registered BroadcastReceiver until probably other 10 min, I know iths finished because, it's visible on the notifications section of the OS.
Has anyone faced this or know how to make DownloadManager behave as its expected?
I've considered rewriting the component so I don't depend on DownloadManager but that exactly what Im trying to avoid.
This is my implementation:
Permissions:
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE " />
DownloadManager implementation
/**
* Manages all DB retrieval and file level validation.
*
*/
class DataBaseInitializationRepository(private val app: Application, private val dbHelper: DatabaseFileHelper) {
fun initiateDB( callback: DownloadCompleteCallback){
val dbUrl = dbHelper.getDbUrl()
val tempDbFile = dbHelper.getTempDbFile()
val permanentDbFile = dbHelper.getPermanentDbFile()
if (!permanentDbFile.exists() && tempDbFile.length() <= 0) {
val downloadManager = app.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val downLoadDBRequest = DownloadManager.Request(Uri.parse( dbUrl ))
.setTitle( app.getString( R.string.download_db_title ) )
.setDescription(app.getString( R.string.download_db_description ))
.setDestinationInExternalFilesDir( app,
null,
tempDbFile.path
)
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
val downloadId = downloadManager.enqueue( downLoadDBRequest )
registerReceiver( callback, downloadId )
}else{
callback.onComplete(0L)
}
}
private fun registerReceiver(callback: DownloadCompleteCallback, downloadId: Long){
val receiver = object: BroadcastReceiver(){
override fun onReceive(context: Context?, intent: Intent?) {
val id = intent?.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
if (id == downloadId){
//Move index reads to reusable function
val downloadManager = app.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val query = DownloadManager.Query()
query.setFilterById( downloadId )
val data = downloadManager.query( query )
if(data.moveToFirst() && data.count > 0){
val statusIndex = data.getColumnIndex(DownloadManager.COLUMN_STATUS)
val status = data.getInt( statusIndex )
if(status == DownloadManager.STATUS_SUCCESSFUL){
val localUriIndex = data.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)
val localFile = File(
data.getString( localUriIndex )
.replace("file://","" )
)
if(localFile.exists()){
permanentlyStoreDb(localFile)
callback.onComplete(id)
}else{
callback.onFail("Initial Database Download Failed - File not found")
}
}else if(status == DownloadManager.STATUS_FAILED){
val reasonIndex = data.getColumnIndex(DownloadManager.COLUMN_REASON)
val reason = data.getInt( reasonIndex )
if(reason == DownloadManager.ERROR_FILE_ALREADY_EXISTS){
callback.onComplete(id)
}else{
callback.onFail("Initial Database Download Failed: $reason")
}
}
}else{
callback.onFail("Initial Database Download Failed - Unable to read download metadata")
}
}
}
}
app.registerReceiver( receiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE) )
}
private fun permanentlyStoreDb(tempFile: File): File {
val permanentDbFile = dbHelper.getPermanentDbFile()
try {
if(tempFile.exists()) {
tempFile.copyTo(
permanentDbFile,
true,
1024
)
tempFile.delete()
}else{
throw IOException("Temporal DB file doesn't exist")
}
}catch (ioex: IOException) {
throw IOException("Unable to copy DB to permanent storage:", ioex)
}
return permanentDbFile
}
/**
* Allows download completion to be notified back to the calling view model
*/
interface DownloadCompleteCallback{
fun onComplete(downloadId: Long)
fun onFail(message: String)
}
}
DatabaseFileHelper contains the logic to determine the temporary file, the permanent DB location and the DB URL where the download will happen. This is the logic I used for the temporary file:
fun getTempDbFile(): File {
return File.createTempFile(<FILE-LOCATION>, null, app.cacheDir)
}
My app uses DownloadManager to download a file , it works perfectly in my real phone.
But downloading is always pending in AVD, it never starts to download.
I'm using Android Studio 4.0.1, and I have created several AVDs, none of them works.
In the AVDs, web browsing works, so the networks should be no problem.
Besides my own app, I have tried several DownloadManager demos from github, none of them works in AVDs.
Help, please.
class MainActivity : AppCompatActivity() {
private val TAG = "MY"
private var enqueue: Long = 0
private lateinit var dm: DownloadManager
inner class MyReceiver: BroadcastReceiver() {
override fun onReceive(context: Context, intent:Intent) {
Log.d(TAG, "Received.")
val action = intent.getAction()
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
val downloadId = intent.getLongExtra(
DownloadManager.EXTRA_DOWNLOAD_ID, 0);
val query = DownloadManager.Query();
query.setFilterById(enqueue);
val c = dm.query(query);
if (c.moveToFirst()) {
val columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) {
val view = findViewById<ImageView>(R.id.imageView1)
val uriString = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
view.setImageURI(Uri.parse(uriString));
}
}
}
if (DownloadManager.ACTION_NOTIFICATION_CLICKED.equals(action)) {
Log.d(TAG,"clicked")
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val receiver = MyReceiver()
val f = IntentFilter()
f.addAction( DownloadManager.ACTION_DOWNLOAD_COMPLETE)
f.addAction( ACTION_NOTIFICATION_CLICKED)
registerReceiver(receiver, f)
btDownload.setOnClickListener {
onClick(it)
}
}
fun onClick(view: View) {
dm = getSystemService(DOWNLOAD_SERVICE) as DownloadManager ;
val request = DownloadManager.Request(
Uri.parse("http://www.example.com/abd3234dfdfwefwef.pdf"));
enqueue = dm.enqueue(request);
}
}
I have solved this problem by myself.
In Android Studio (4.0.1 currently), if the AVD images is api v26+, I must set allowed network type to DownloadManager.Request.NETWORK_WIFI.
But these two don't work:
DownloadManager.Request.NETWORK_WIFI
and
DownloadManager.Request.NETWORK_WIFI + DownloadManager.Request.NETWORK_MOBILE
What I need to do:
Download a file by URL using DownloadManager, and separetely handle successful download and error download.
What I tried:
I used BroadcastReceiver to catch the result of file download. I tried to use DownloadManager.ACTION_DOWNLOAD_COMPLETE as a marker but it fires not only when file is successfully downloaded but also when error occured and no file was downloaded.
So it seems like DownloadManager.ACTION_DOWNLOAD_COMPLETE reports only that attempt to download was made no matter with what result.
Is there a way to catch only successful downloads?
my code:
fragment.kt
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
downloadCompleteReceiver = object : BroadcastReceiver(){
override fun onReceive(context: Context?, intent: Intent?) {
Snackbar.make(requireActivity().findViewById(android.R.id.content), getString(R.string.alert_files_successfully_downloaded), Snackbar.LENGTH_LONG).show()
}
}
val filter = IntentFilter()
filter.addAction(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
requireActivity().registerReceiver(downloadCompleteReceiver, filter)
}
Request:
fun downloadMediaFiles(listOfUrls: List<MediaDto>, activity: Activity, authToken:String) {
if (isPermissionStorageProvided(activity)) {
val manager = activity.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
listOfUrls.forEach {
val request = DownloadManager.Request(Uri.parse(it.url))
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE)
request.setTitle(activity.getString(R.string.download_manager_title))
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
request.setDestinationInExternalPublicDir(
getDestinationDirectoryFromFileExtension(it.url),
"${System.currentTimeMillis()}"
)
request.addRequestHeader("authorization", authToken)
manager.enqueue(request)
}
}
}
SOLVED
What Rediska wrote + need also add this to my BroadcastReceiver object:
val referenceId = intent!!.getLongExtra(
DownloadManager.EXTRA_DOWNLOAD_ID, -1L
)
and then pass this referenceId to getDownloadStatus as an arguement.
getDownloadStatus returns integer of 8 when successfull and 16 if failure, which I can further process.
This function will return the status of the download. See DownloadManager for values. It returns -1 if the download not found for given id.
int getDownloadStatus(long id) {
try {
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(id);
DownloadManager downloadManager = (DownloadManager)context.getSystemService(Context.DOWNLOAD_SERVICE);
Cursor cursor = downloadManager.query(query);
if (cursor.getCount() == 0) return -1;
cursor.moveToFirst();
return cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
} catch(Exception ex) { return -1; }
}
I've been beating my head against this issue for quite awhile... I am updating an app that uses DownloadManger to do a simple task like downloading a file to the external storage public directory i.e:
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
Everything works fine here from Android api 19-28. Its when testing on API 29 (Q/10) is where issues occur. Android implemented scoped storage and so deprecated the getExternalStoragePublicDirectory... As a result I need to figure out a compatible solution to support APIs 19-29. I cannot use internal application storage since DownloadManager will throw a SecurityException. Androids documentation states that I can use the DownloadManager.Request setDestinationUri and it even mentions for Android Q that I can use Context.getExternalFilesDir(String). When I do this though, the path is still the emulated path:
/storage/emulated/0/Android/data/com.my.package.name/files/Download/myFile.xml
I get a callback from the download manager that the download is complete (with right ID) but then I cannot grab the download from the area I saved it to. I check to see if the file exists and it returns false:
new File("/storage/emulated/0/Android/data/com.my.package.name/files/Download/myFile.xml").exists();
Any help is appreciated
Adding code for context. So setting up download manager
private void startDownload() {
IntentFilter filter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
registerReceiver(downloadReceiver, filter);
String remoteURL= getString(R.string.remote_url);
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(remoteUrl));
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE);
request.setTitle(getString(R.string.download_title));
request.setDescription(getString(R.string.download_description));
request.setDestinationUri(Uri.fromFile(new File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "myFile.xml")));
DownloadManager manager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
mainDownloadID= manager.enqueue(request);
}
checking file if it exists:
new File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "myFile.xml").exists(); //this returns false in the onReceive (and download IDs match)
Try add this into your manifest file in application tag
android:requestLegacyExternalStorage="true"
File Paths outside of the App's private directories in Android Q and above useless.
See https://developer.android.com/training/data-storage#scoped-storage
You need to ask the user where to download the files too, this will get you a URI for the DownloadManager destination.
https://developer.android.com/training/data-storage/shared/documents-files#grant-access-directory
You will probably want to persist this permission
https://developer.android.com/training/data-storage/shared/documents-files#persist-permissions
Yeah Its scope storage but even though you can download file in Q+ using downloadmanger no need to do android:requestLegacyExternalStorage="true"
I am doing this way.
manifest
-->
Downloadmanger
val fileName =
Constants.FILE_NAME + Date().time
val downloadUri = Uri.parse(media.url)
val request = DownloadManager.Request(
downloadUri
)
request.setAllowedNetworkTypes(
DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE
)
.setAllowedOverRoaming(true).setTitle("Some name")
.setDescription("Downloading file")
.setDestinationInExternalPublicDir(
Environment.DIRECTORY_DOWNLOADS, File.separator + FOLDER + File.separator + fileName
)
Toast.makeText(
context,
"Download successfully to ${downloadUri?.path}",
Toast.LENGTH_LONG
).show()
downloadManager.enqueue(request)
Hence it will ask write permission below Q, but in Q and Q+ it will download without asking permission in /Download/folder dir.
Use this code and enjoy, this code uses RxJava for network call:
import android.content.ContentValues
import android.content.Context
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import io.reactivex.Observable
import io.reactivex.ObservableEmitter
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.ResponseBody
import java.io.*
import java.net.HttpURLConnection
import java.util.concurrent.TimeUnit
class FileDownloader(
private val context: Context,
private val url: String,
private val fileName: String
) {
private val okHttpClient: OkHttpClient = OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.build()
private val errorMessage = "File couldn't be downloaded"
private val bufferLengthBytes: Int = 1024 * 4
fun download(): Observable<Int> {
return Observable.create<Int> { emitter ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // To Download File for Android 10 and above
val content = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
}
val uri = context.contentResolver.insert(
MediaStore.Downloads.EXTERNAL_CONTENT_URI,
content
)
uri?.apply {
val responseBody = getResponseBody(url)
if (responseBody != null
) {
responseBody.byteStream().use { inputStream ->
context.contentResolver.openOutputStream(uri)?.use { fileOutStream ->
writeOutStream(
inStream = inputStream,
outStream = fileOutStream,
contentLength = responseBody.contentLength(),
emitter = emitter
)
}
emitter.onComplete()
}
} else {
emitter.onError(Throwable(errorMessage))
}
}
}
else { // For Android versions below than 10
val directory = File(
Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS).absolutePath
).apply {
if (!exists()) {
mkdir()
}
}
val file = File(directory, fileName)
val responseBody = getResponseBody(url)
if (responseBody != null) {
responseBody.byteStream().use { inputStream ->
file.outputStream().use { fileOutStream ->
writeOutStream(
inStream = inputStream,
outStream = fileOutStream,
contentLength = responseBody.contentLength(),
emitter = emitter
)
}
emitter.onComplete()
}
} else {
emitter.onError(Throwable(errorMessage))
}
}
}
}
private fun getResponseBody(url: String): ResponseBody? {
val response = okHttpClient.newCall(Request.Builder().url(url).build()).execute()
return if (response.code >= HttpURLConnection.HTTP_OK &&
response.code < HttpURLConnection.HTTP_MULT_CHOICE &&
response.body != null
)
response.body
else
null
}
private fun writeOutStream(
inStream: InputStream,
outStream: OutputStream,
contentLength: Long,
emitter: ObservableEmitter<Int>) {
var bytesCopied = 0
val buffer = ByteArray(bufferLengthBytes)
var bytes = inStream.read(buffer)
while (bytes >= 0) {
outStream.write(buffer, 0, bytes)
bytesCopied += bytes
bytes = inStream.read(buffer)
// emitter.onNext(
((bytesCopied * 100) / contentLength).toInt()
// )
}
outStream.flush()
outStream.close()
}
}
On calling side you've to right this:
private fun downloadFileFromUrl(context: Context, url: String, fileName: String) {
FileDownloader(
context = context,
url = url,
fileName = fileName
).download()
.throttleFirst(2, TimeUnit.SECONDS)
.toFlowable(BackpressureStrategy.LATEST)
.subscribeOn(Schedulers.io())
.observeOn(mainThread())
.subscribe({
// onNext: Downloading in progress
}, { error ->
// onError: Download Error
requireContext()?.apply {
Toast.makeText(this, error.message, Toast.LENGTH_SHORT).show()
}
}, {
// onComplete: Download Complete
requireContext()?.apply {
Toast.makeText(this, "File downloaded to Downloads Folder", Toast.LENGTH_SHORT).show()
}
})
}
I am trying to download a file using Android's DownloadManager and trying to print the download tho the console using log statements.
Though the file is downloaded properly, i am not able to see the log statement of download's progress
Here is my code
private fun downloadPdf(fileName: String?, fileExtension: String?, destinationDirectory: String?, url: String?) {
val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val uri = Uri.parse(url)
val request = DownloadManager.Request(uri)
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
request.setDestinationInExternalPublicDir(destinationDirectory, fileName + fileExtension)
val downloadId = downloadManager.enqueue(request)
thread {
val query = DownloadManager.Query()
query.setFilterById(downloadId)
val cursor = downloadManager.query(query)
if(cursor.moveToFirst()){
val sizeIndex = cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)
val downloadedIndex = cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)
val size = cursor.getInt(sizeIndex)
val downloaded = cursor.getInt(downloadedIndex)
val progress: Long
if(size != -1){
progress = downloaded * 100L / size
runOnUiThread {
Log.i("pritishsawantprogress",progress.toString())
}
}
}
}
}
Any help would be greatly appreciated
private fun downloadPdf(fileName: String?, fileExtension: String?, destinationDirectory: String?, url: String?) {
val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val uri = Uri.parse(url)
val request = DownloadManager.Request(uri)
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
request.setDestinationInExternalPublicDir(destinationDirectory, fileName + fileExtension)
val downloadId = downloadManager.enqueue(request)
thread {
var downloading = true
while (downloading){
val query = DownloadManager.Query()
query.setFilterById(downloadId)
val cursor = downloadManager.query(query)
if(cursor.moveToFirst()){
val bytesDownloaded = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
val bytesTotal = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
if(cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)) == DownloadManager.STATUS_SUCCESSFUL){
downloading = false
}
val progress = ((bytesDownloaded * 100L)/bytesTotal).toInt()
runOnUiThread {
Log.i("pritishsawantprogress",progress.toString())
}
cursor.close()
}
}
}
}