cannot open file downloaded using download manager API inside android webview - android

I am downloading pdf files inside web view in android studio. That's going well. I am successfully downloading a pdf file with the .pdf extension but when I click on the file see a toast message something went wrong can't open this file
Here is my code
binding?.webView?.setDownloadListener { url, userAgent, contentDisposition, mimetype, contentLength ->
val pdfUrl:String = url.trim().replace("blob:","")
Log.e(TAG, "loadWAWeb: $pdfUrl")
if (storagePermission()) {
// binding?.webView?.loadUrl(JavaScriptInterface.getBase64StringFromBlobUrl(url,mimetype))
val request = DownloadManager.Request(Uri.parse(pdfUrl))
request.setMimeType(mimetype)
request.allowScanningByMediaScanner()
request.setDescription("Downloading file...")
val fileName = URLUtil.guessFileName(pdfUrl, contentDisposition, mimetype)
request.setTitle(fileName)
Log.e(TAG, "loadWAWeb: $fileName" )
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
val dm = getSystemService(DOWNLOAD_SERVICE) as DownloadManager
dm.enqueue(request)
}
}
this is a pdf file which is good in format but I don't know where is the issue.
but when I try to open this file. This issue that I am facing.

I Don't see that have a problem, maybe the pdf file just corrupted. Test in another PDF File or just update your Android Studio. in my case before i am implement Fast Android Networking the file sometimes corrupted, but after i am implement Fast Android Networking the file is not corrupted.
in Gradle, paste this
implementation 'com.amitshekhar.android:android-networking:1.0.2'
and add Internet Permission on Manifest.xml
<uses-permission android:name="android.permission.INTERNET" />
Then initialize it in onCreate() Method of application class
AndroidNetworking.initialize(getApplicationContext());
i think my explanation is not that good, you can see at https://github.com/amitshekhariitbhu/Fast-Android-Networking
im sorry if this not make you problem solved, but based on my case it solved

Continuously my downloaded file was corrupting I handled this as:
JavaScriptInterface.kt
package com.hamza.hiwhatsappweb.utils
import android.R
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Environment
import android.os.Handler
import android.util.Base64
import android.util.Log
import android.webkit.JavascriptInterface
import android.widget.Toast
import androidx.core.app.NotificationCompat
import androidx.core.content.FileProvider
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
class JavaScriptInterface(private val context: Context) {
#JavascriptInterface
#Throws(IOException::class)
fun getBase64FromBlobData(base64Data: String) {
Log.e("mimeType", "getBase64FromBlobData: $mimTp")
convertBase64StringToPdfAndStoreIt(base64Data)
}
#Throws(IOException::class)
private fun convertBase64StringToPdfAndStoreIt(base64PDf: String) {
Log.e("BASE 64", base64PDf)
val notificationId = 1
val dwldsPath = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString() + "/$fileName")
val pdfAsBytes =
Base64.decode(base64PDf.replaceFirst("^data:$mimTp;base64,".toRegex(), ""), 0)
val os = FileOutputStream(dwldsPath, false)
os.write(pdfAsBytes)
os.flush()
if (dwldsPath.exists()) {
val intent = Intent()
intent.action = Intent.ACTION_VIEW
val apkURI = FileProvider.getUriForFile(context, context.applicationContext.packageName + ".provider", dwldsPath)
intent.setDataAndType(apkURI, mimTp)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
val pendingIntent =
PendingIntent.getActivity(context, 1, intent, PendingIntent.FLAG_CANCEL_CURRENT)
val CHANNEL_ID = "MYCHANNEL"
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationChannel =
NotificationChannel(CHANNEL_ID, "name", NotificationManager.IMPORTANCE_HIGH)
val notification = Notification.Builder(
context, CHANNEL_ID
)
.setContentText(fileName)
.setContentTitle("File downloaded")
.setContentIntent(pendingIntent)
.setChannelId(CHANNEL_ID)
.setSmallIcon(R.drawable.sym_action_chat)
.build()
if (notificationManager != null) {
notificationManager.createNotificationChannel(notificationChannel)
notificationManager.notify(notificationId, notification)
}
} else {
val b = NotificationCompat.Builder(context, CHANNEL_ID)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.sym_action_chat) //.setContentIntent(pendingIntent)
.setContentTitle("MY TITLE")
.setContentText("MY TEXT CONTENT")
if (notificationManager != null) {
notificationManager.notify(notificationId, b.build())
val h = Handler()
val delayInMilliseconds: Long = 1000
h.postDelayed({ notificationManager.cancel(notificationId) }, delayInMilliseconds)
}
}
}
Toast.makeText(context, "PDF FILE DOWNLOADED!", Toast.LENGTH_SHORT).show()
}
companion object {
var mimTp:String = ""
var fileName:String = ""
fun getBase64StringFromBlobUrl(blobUrl: String, mimeType: String,fileName:String): String {
mimTp = mimeType
this.fileName = fileName
return if (blobUrl.startsWith("blob")) {
"javascript: var xhr = new XMLHttpRequest();" +
"xhr.open('GET', '" + blobUrl + "', true);" +
"xhr.setRequestHeader('Content-type','$mimeType');" +
"xhr.responseType = 'blob';" +
"xhr.onload = function(e) {" +
" if (this.status == 200) {" +
" var blobPdf = this.response;" +
" var reader = new FileReader();" +
" reader.readAsDataURL(blobPdf);" +
" reader.onloadend = function() {" +
" base64data = reader.result;" +
" Android.getBase64FromBlobData(base64data);" +
" }" +
" }" +
"};" +
"xhr.send();"
} else "javascript: console.log('It is not a Blob URL');"
}
}
}
Set this property on Webview:
binding?.webView?.addJavascriptInterface(JavaScriptInterface(applicationContext), "Android")
binding?.webView?.settings?.pluginState = WebSettings.PluginState.ON
binding?.webView?.setDownloadListener { url, _, contentDisposition, mimetype, _ ->
// downloadFiles(url, mimetype, contentDisposition)
val pdfUrl: String? = url?.trim()?.replace("blob:", "")
val fileName = URLUtil.guessFileName(pdfUrl, contentDisposition, mimetype)
binding?.webView?.loadUrl(JavaScriptInterface.getBase64StringFromBlobUrl(url,mimetype,fileName))
}
provider_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="external_files" path="."/>
</paths>
Paste this code under Application in AndroidManifest.xml file
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths" />
</provider>

Related

Install apk update from firbase | Android Kotlin

I have an android app that not on Google play and I want it to get updates using APK on the server.
This code is working and checks for updates by searching for APK on Firebase storage with greater version than the current build and ending it to download
private fun checkUpdates(){
val storage = FirebaseStorage.getInstance()
val storageRef = storage.reference
val rootRef = storageRef.root
rootRef.listAll().addOnSuccessListener { listResult ->
for (item in listResult.items) {
// item is a StorageReference for a file
val fileName = item.name
val version = fileName.substring(fileName.indexOf("app_v") + 5, fileName.indexOf(".apk"))
if (version.replace(".", "") > BuildConfig.VERSION_CODE.toString().replace(".", "")) {
val builder = AlertDialog.Builder(this)
builder.setMessage(R.string.new_up_av)
builder.setPositiveButton(R.string.install) { _, _ ->
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), REQUEST_WRITE_STORAGE
)
download(version)
} else {
download(version)
}
}
val dialog = builder.create()
dialog.show()
}
else{
Toast.makeText(this, R.string.no_updates, Toast.LENGTH_LONG).show()
}
}
} .addOnFailureListener{
Toast.makeText(this, "ERROR", Toast.LENGTH_SHORT).show()
}
}
The download function is this (notice the comment on line 4):
private fun download(version: String) {
Toast.makeText(this, R.string.download_updates, Toast.LENGTH_SHORT).show()
val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val downloadUri = Uri.parse(//url for apk here.)
val request = DownloadManager.Request(downloadUri)
.setTitle(getString(R.string.download_updates))
.setDescription(getString(R.string.app_name))
val downloadId = downloadManager.enqueue(request)
// Check if the download was successful
if (downloadId == -1L) {
// Download failed, show an error message
Toast.makeText(this, "download failed", Toast.LENGTH_SHORT).show()
return
}
// Listen for the download to complete and install the update
val downloadReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
if (downloadId == id) {
installApk(context, "app_v$version.apk")
}
}
}
registerReceiver(downloadReceiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
}
on line 4 i verified it works manually and the problems is showing also with sure-working APKs like WhatsApp.
the installApk function:
fun installApk(context: Context, fileName: String) {
val file = File(Environment.getExternalStorageDirectory().toString() + "/Download/" + filename)
val fileUri = FileProvider.getUriForFile(context, "${context.packageName}.provider", file)
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(fileUri, "application/vnd.android.package-archive")
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
}
provider in Manifest:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android: resource="#xml/provider_paths" />
</provider>
provider_paths.xml:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="downloads" path="Download/" />
</paths>
The problem: seems like its downloading, starting to install, and than shows an error occurred while parsing the package message. no logs.
(It works manually)
I have tried many other ways buts I couldn't figure what cussing the problem.
If someone fammiliar with intalling apk programmatically I looking for help.

Open PDF File from Download Manager Located from Download

I am using DownloadManager to download a file from FirebaseStorage.
First, I will get the downloadUrl from FirebaseStorage and proceed with DownloadManager
As you can see codes below, this is where after I got the downloadUrl as url.
downloadManager = getSystemService(DOWNLOAD_SERVICE) as DownloadManager
val uri = Uri.parse(url)
val request = DownloadManager.Request(uri)
val folderName = File.separator + "MBITION" + File.separator + fileName + fileExtension
name = folderName
Log.i("???", "url: $url")
Log.i("???", "folderName: $folderName")
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
request.setDestinationInExternalPublicDir(
Environment.DIRECTORY_DOWNLOADS,
folderName
)
enq = downloadManager!!.enqueue(request)
Logcat below shows the url and folderName value.
I/???: url: https://firebasestorage.googleapis.com/v0/b/mbition-2022.appspot.com/o/note%2F-N2_fRAhJXVshDjQMcrz?alt=media&token=936e1014-6c7a-4f46-89fd-5746eb6a9dbf
I/???: folderName: /MBITION/ICT600 - Chapter 3.pdf
As you can see codes below, where onReceive from BroadcastReceiver is handled.
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE == action) {
val downloadId = it.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0)
val query = DownloadManager.Query()
query.setFilterById(downloadId)
val c: Cursor = downloadManager!!.query(query)
if (c.moveToFirst()) {
val columnIndex: Int = c.getColumnIndex(DownloadManager.COLUMN_STATUS)
if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) {
val uriString: String = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
val file = File(uriString)
val target = Intent(Intent.ACTION_VIEW)
Log.i("???", "uriString:: $uriString")
Log.i("???", "file:: $file")
val uri = FileProvider.getUriForFile(
this#NoteActivity,
"$packageName.provider",
file
)
target.setDataAndType(uri, "application/pdf")
target.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
val intentChooser = Intent.createChooser(target, "Open File")
try {
startActivity(intentChooser)
} catch (e: ActivityNotFoundException) {
Tools.showToast(
this#NoteActivity,
"You need to download PDF reader"
)
}
}
}
}
Below shows the Logcat.
I/???: uriString:: file:///storage/emulated/0/Download/MBITION/ICT600%20-%20Chapter%203-5.pdf
I/???: file:: file:/storage/emulated/0/Download/MBITION/ICT600%20-%20Chapter%203-5.pdf
This is my provider_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="external_files"
path="." />
</paths>
I tried to open the file through the DownloadManager's Notification. It is working fine.
Image below shows where the file is located inside the MBITION folder.
Below is the error I got from Logcat.
2022-05-28 11:36:44.534 4999-4999/com.aaa.mbition E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.aaa.mbition, PID: 4999
java.lang.IllegalArgumentException: Failed to find configured root that contains /file:/storage/emulated/0/Download/MBITION/ICT600%20-%20Chapter%203.pdf
at androidx.core.content.FileProvider$SimplePathStrategy.getUriForFile(FileProvider.java:800)
at androidx.core.content.FileProvider.getUriForFile(FileProvider.java:442)
at com.aaa.mbition.ui.NoteActivity$onCompleteDownloadFile$1$onReceive$1.invoke(NoteActivity.kt:79)
Updated:
Refer to #blackapps 's suggestion. I tried to remove file:// by using replace like below:
val uriString: String = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
val urlFixer = uriString.replace("file://","")
val file = File(urlFixer)
val target = Intent(Intent.ACTION_VIEW)
Log.i("???", "uriString:: $uriString")
Log.i("???", "file:: $file")
Log.i("???", "urlFixer:: $urlFixer")
val uri = FileProvider.getUriForFile(
this#NoteActivity,
"$packageName.provider",
file
)
Below is the Logcat
I/???: uriString:: file:///storage/emulated/0/Download/MBITION/ICT600%20-%20Chapter%203-9.pdf
I/???: file:: /storage/emulated/0/Download/MBITION/ICT600%20-%20Chapter%203-9.pdf
I/???: urlFixer:: /storage/emulated/0/Download/MBITION/ICT600%20-%20Chapter%203-9.pdf
When I pressed OK button, it proceed to PDF Reader, a few milisecond, it kick me back to the apps.
According to #blackapps answer is working. I fixed them by using replace.
val uriString: String = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
val urlFixer = uriString.replace("file://", "").replace("%20", " ")
val file = File(urlFixer)
val target = Intent(Intent.ACTION_VIEW)

android - download base64 encoded pdf file and open it

I'm trying to find a solution to simply download a base64 encoded pdf file of a webservice and open it with an preinstalled pdf viewer. My application targets Android R. I tried something like this but I dont't want a picker to open.
This is my code so far. It is just downloading the file and converts it to a bytearray. The next step should by saving the file and opening it.
lifecycleScope.launch {
withContext(Dispatchers.IO) {
try {
Snackbar.make(binding.root, getString(R.string.load_document_started), Snackbar.LENGTH_LONG).show()
val documentData = DocumentDao().get(document.id, montageOrder)
val docAsByte = Base64.decode(documentData.data, Base64.DEFAULT)
val currentDateString = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
val fileName = document.documentType.
lowercase()
.replace("ä", "ae")
.replace("ü", "ue")
.replace("ö", "oe") +
"_" + currentDateString
val file = File(requireContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), fileName)
val fileStream = FileOutputStream(file)
fileStream.write(docAsByte)
fileStream.close()
val target = Intent(Intent.ACTION_VIEW)
target.setDataAndType(Uri.fromFile(file), "application/pdf")
target.flags = Intent.FLAG_ACTIVITY_NO_HISTORY
val intent = Intent.createChooser(target, "Yolo");
startActivity(intent)
} catch (e: Exception) {
Log.e(TAG, "Dokument konnte nicht geladen werden: " + e.message, e)
Snackbar.make(binding.root, getString(R.string.exception_could_not_load_document), Snackbar.LENGTH_LONG).show()
}
}
}
This results in a FileUriExposedException
Another aproach was using the SAF
lateinit var docAsByte : ByteArray
private val createFileLauncher = registerForActivityResult(CreatePdfDocument()) { uri ->
lifecycleScope.launch {
withContext(Dispatchers.IO) {
val stream = requireContext().contentResolver.openOutputStream(uri)
stream?.write(docAsByte)
stream?.close()
val target = Intent(Intent.ACTION_VIEW)
target.setDataAndType(uri, "application/pdf")
target.flags = Intent.FLAG_ACTIVITY_NO_HISTORY
startActivity(target)
}
}
}
private fun setGui() {
_binding?.lvDocuments?.adapter = DocumentAdapter(requireContext(), montageOrder.documents)
_binding?.lvDocuments?.setOnItemClickListener { parent, _, position, _ ->
val document : Document = parent.getItemAtPosition(position) as Document
lifecycleScope.launch {
withContext(Dispatchers.IO) {
try {
val currentDateString = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
val fileName = document.documentType.
lowercase()
.replace("ä", "ae")
.replace("ü", "ue")
.replace("ö", "oe") +
"_" +
montageOrder.orderNumber +
"_" +
currentDateString +
".pdf"
Snackbar.make(binding.root, getString(R.string.load_document_started), Snackbar.LENGTH_LONG).show()
val documentData = DocumentDao().get(document.id, montageOrder)
docAsByte = Base64.decode(documentData.data, Base64.DEFAULT)
createFileLauncher.launch(fileName)
} catch (e: Exception) {
Log.e(TAG, "Dokument konnte nicht geladen werden: " + e.message, e)
Snackbar.make(binding.root, getString(R.string.exception_could_not_load_document), Snackbar.LENGTH_LONG).show()
}
}
}
}
}
Everything works fine except for opening. But if I open the pdf via file explorer it works.
Found a thread online and solved it this way: https://www.py4u.net/discuss/614761
Add provider_paths.xml to xml resource folder
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
In your manifest add a FileProvider:
<application>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths" />
</provider>
</application>
Prepare to download files to any directory your app owns, such as getFilesDir(), getExternalFilesDir(), getCacheDir() or getExternalCacheDir().
val privateDir = context.getFilesDir()
Download file taking its progress into account (DIY):
val downloadedFile = myFancyMethodToDownloadToAnyDir(url, privateDir, fileName)
Copy it to Downloads folder:
private val DOWNLOAD_DIR = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val finalUri : Uri? = copyFileToDownloads(context, downloadedFile)
fun copyFileToDownloads(context: Context, downloadedFile: File): Uri? {
val resolver = context.contentResolver
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, getName(downloadedFile))
put(MediaStore.MediaColumns.MIME_TYPE, getMimeType(downloadedFile))
put(MediaStore.MediaColumns.SIZE, getFileSize(downloadedFile))
}
resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
} else {
val authority = "${context.packageName}.provider"
val destinyFile = File(DOWNLOAD_DIR, getName(downloadedFile))
FileProvider.getUriForFile(context, authority, destinyFile)
}?.also { downloadedUri ->
resolver.openOutputStream(downloadedUri).use { outputStream ->
val brr = ByteArray(1024)
var len: Int
val bufferedInputStream = BufferedInputStream(FileInputStream(downloadedFile.absoluteFile))
while ((bufferedInputStream.read(brr, 0, brr.size).also { len = it }) != -1) {
outputStream?.write(brr, 0, len)
}
outputStream?.flush()
bufferedInputStream.close()
}
}
Once in download folder you can open file from app like this:
val authority = "${context.packageName}.provider"
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(finalUri, getMimeTypeForUri(finalUri))
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) {
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION)
} else {
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
}
try {
context.startActivity(Intent.createChooser(intent, chooseAppToOpenWith))
} catch (e: Exception) {
Toast.makeText(context, "Error opening file", Toast.LENGTH_LONG).show()
}
//Kitkat or above
fun getMimeTypeForUri(context: Context, finalUri: Uri) : String =
DocumentFile.fromSingleUri(context, finalUri)?.type ?: "application/octet-stream"
//Just in case this is for Android 4.3 or below
fun getMimeTypeForFile(finalFile: File) : String =
DocumentFile.fromFile(it)?.type ?: "application/octet-stream"

DownloadManager not working for Android 10 (Q)

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

how to download and install an APK file by an intent?

I am making my app to be updated via my own file server instead of Google Play-store. However, it does not work well. After confirming to "update", APK file is downloaded, it is not opened correctly. Please check the demonstration https://youtu.be/qDSGZ9fQ1Oo
class MainActivity : Activity() {
private fun checkUpdate(){
val jsonObjectRequest = JsonObjectRequest(
Request.Method.GET,
"https://myserver/release.json",
null,
Response.Listener { response ->
if(response.getInt("version") > versionCode){
val builder = AlertDialog.Builder(this, R.style.Theme_AppCompat_Dialog_Alert)
builder.setTitle("Update to v" + response.getString("version") + " ?")
builder.setMessage(response.getString("note"))
builder.setPositiveButton("Yes") { _, _ ->
downloadUpdate(response.getString("version"))
}
builder.setNegativeButton("No") { _, _ ->
showUserInteraction()
}
builder.setCancelable(false)
builder.show()
}else{
showUserInteraction()
}
},
Response.ErrorListener{ _ ->
showUserInteraction()
}
)
requestQueue.add(jsonObjectRequest)
}
private fun downloadUpdate(versionCode: String) {
registerReceiver(onDownloadComplete(), IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
val request = DownloadManager
.Request(Uri.parse("https://myserver/app-release.apk"))
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "myapp_v" + versionCode + ".apk")
downloadManager = getSystemService(DOWNLOAD_SERVICE) as DownloadManager
downloadId = downloadManager.enqueue(request)
}
private class onDownloadComplete: BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val c = downloadManager.query(DownloadManager.Query().setFilterById(downloadId))
if(c != null){
c.moveToFirst()
val fileUri = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
val mFile = File(Uri.parse(fileUri).path!!)
val fileName = mFile.absolutePath
context.unregisterReceiver(this)
val intent = Intent(Intent.ACTION_VIEW)
var contentUri: Uri
if (SDK_VER >= Build.VERSION_CODES.N) {
intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
contentUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileProvider", File(fileName))
}else{
contentUri = Uri.fromFile(File(fileName))
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
intent.setDataAndType(contentUri, "application/vnd.android.package-archive")
startActivity(context, intent, null)
}
}
}
}
May anyone please point out my mistake? Thanks.
You need to add below permission in your manifest.xml file.
If an app uses a targetSdkLevel of 26 or above and prompts the user to install other apps, the manifest file needs to include the REQUEST_INSTALL_PACKAGES permission:
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
You can see below link why it's needed
Link1
Link2

Categories

Resources