I have to open a PDF from a URL that requires an access token (The PDF is not publically available). The PDF should not be permanently stored on the device.
If the access token wouldn't be required, the solution would look like this:
val intent = Intent(Intent.ACTION_VIEW).apply { setDataAndType(Uri.parse(url), "application/pdf") }
startActivity(intent)
However, an access token is required and therefore this doesn't work.
I have tried downloading the file in the app and then opening the local copy of the file, like this:
val inputStream = /*get PDF as an InputStream*/
val file = File.createTempFile(
fileName,
".pdf",
context?.externalCacheDir
).apply { deleteOnExit() }
stream.use { input ->
file.outputStream().use { output ->
input.copyTo(output)
}
}
val intent = Intent(Intent.ACTION_VIEW).apply { setDataAndType(Uri.fromFile(file), "application/pdf") }
startActivity(intent)
but this gives me a android.os.FileUriExposedException with the message <myFilePath> exposed beyond app through Intent.getData().
I have also tried the same thing using context?.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) and got the same result.
So far, the closest I got was like this:
val inputStream = /*get PDF as an InputStream*/
val file = File.createTempFile(
fileName,
".pdf",
context?.filesDir
).apply { deleteOnExit() }
stream.use { input ->
file.outputStream().use { output ->
input.copyTo(output)
}
}
val intent = Intent(Intent.ACTION_VIEW).apply { setDataAndType(Uri.parse(file.absolutePath), "application/pdf") }
startActivity(intent)
Doing it like this, my Samsung device offered me Samsung Notes and Microsoft Word to open the PDF and neither of them was actually able to do it. (It just opened these apps and they showed an error that they couldn't find the file they were supposed to open)
The installed PDF reader - the one that would normally open the PDF when using my two lines of code at the very top of this post - wasn't even listed.
So, how do I properly show this PDF?
Edit:
After finding this answer, I added a file Provider and now my code looks like this:
val inputStream = /*get PDF as an InputStream*/
val file = File.createTempFile(
fileName,
".pdf",
context?.getExternalFilesDir(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
Environment.DIRECTORY_DOCUMENTS
else
Environment.DIRECTORY_DOWNLOADS
).apply { deleteOnExit() }
stream.use { input ->
file.outputStream().use { output ->
input.copyTo(output)
}
}
val documentUri: Uri = FileProvider.getUriForFile(
requireContext(),
getString(R.string.file_provider_authority),
file
)
val intent = Intent(Intent.ACTION_VIEW).apply { setDataAndType(documentUri, "application/pdf") }
startActivity(intent)
Now the PDF can be opened properly. However the PDF is empty.
This is how I load the PDF to an InputStream:
interface DocumentsClient {
#GET("/api/something/pdf/{pdf_id}")
#Headers("Accept: application/pdf")
#Streaming
fun loadSomethingPDF(#Path("pdf_id") pdfId: Long): Call<ResponseBody>
}
fun loadSomethingPDF(accessToken: String?, pdfId: Long) =
createDocumentsClient(accessToken).loadSomethingPDF(pdfId)
private fun createDocumentsClient(accessToken: String?): DocumentsClient {
val okClientBuilder = OkHttpClient.Builder()
// add headers
okClientBuilder.addInterceptor { chain ->
val original = chain.request()
val requestBuilder = original.newBuilder()
if (accessToken != null) {
requestBuilder.header("Authorization", "Bearer $accessToken")
}
val request = requestBuilder.build()
chain.proceed(request)
}
val retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.client(okClientBuilder.build())
.build()
return retrofit.create(DocumentsClient::class.java)
}
loadSomethingPdf then gets called like this:
#Throws(IOException::class, IllegalStateException::class)
suspend fun loadSomethingPdf(accessToken: String?, pdfId: Long) = withContext(Dispatchers.IO) {
val call = remoteManager.loadSomethingPDF(accessToken, pdfId)
try {
val response = call.execute()
if (!response.isSuccessful) {
return#withContext null
}
return#withContext response.body()?.byteStream()
} catch (e: IOException) {
throw e
} catch (e: IllegalStateException) {
throw e
}
}
And that's where I get the inputStream from.
Ok, my problem was somewhere completely different.
I needed to grant Uri permissions to allow the other apps to read the Uri I am sending them.
The code just needed this:
intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
Related
I am using the following function for making request and decoding URI from onActivity result . The following function works but freezes the whole screen for many seconds before generating the final file:
// Request code:
fun filePickerRequest3SAF(activity: AppCompatActivity) {
RequestIntentBuilder(IntentInit.OPEN_DOCUMENT) // Intent(Intent.ACTION_OPEN_DOCUMENT)
.addOpenableCategory()//requestIntent.addCategory(Intent.CATEGORY_OPENABLE)
.setFilteringMimeType("video/*")
.addFlagForReadPermission() //requestIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) and requestIntent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
.buildAndStartActivityForResult(activity)
}
//response function. here I pass received intent's URI and activity context
fun getPathFromUriOrEmpty(uri: Uri?, context: Context?): String {
if (context == null || uri == null) return ""
return getFileFromUriOrDefault(uri, context)?.path ?: ""
}
fun getFileFromUriOrDefault(uri: Uri?, context: Context?, default:File? = null): File? {
if (context == null || uri == null) return default
val resolver = context.contentResolver
val tmpFile = File(context.cacheDir, getFileNameFromUriOrNull(uri, resolver) ?: "temp_file_name.${getExtensionFromUriOrDefault(uri, context)}")
return try {
val inputStream = context.contentResolver.openInputStream(uri)
val outputStream = FileOutputStream(tmpFile)
outputStream.use { fileOut -> inputStream?.copyTo(fileOut) }
tmpFile
} catch (t: Throwable) {
t.printStackTrace()
default
}
}
is there a way to do it better, apart from just making a file and copying it as whole? My app is supposed to upload videos of size > 1-2 gb, so is there a way we can provide the URI / file to the upload service without actually making a copy of file? I am assuming the file upload service will also be making multiple copies to upload
Ps: I intent to support android versions KitKat to android 12/+ , so not thinking of using legacy storage or other such flags if they can be avoided with SAF as a unified solution across different android versions
Basically, I am trying to download three different images(bitmaps) from a URL and save them to Apps Internal storage, and then use the URI's from the saved file to save a new Entity to my database. I am having a lot of issues with running this in parallel and getting it to work properly. As ideally all three images would be downloaded, saved and URI's returned simultaneously. Most of my issues come from blocking calls that I cannot seem to avoid.
Here's all of the relevant code
private val okHttpClient: OkHttpClient = OkHttpClient()
suspend fun saveImageToDB(networkImageModel: CBImageNetworkModel): Result<Long> {
return withContext(Dispatchers.IO) {
try {
//Upload all three images to local storage
val edgesUri = this.async {
val req = Request.Builder().url(networkImageModel.edgesImageUrl).build()
val response = okHttpClient.newCall(req).execute() // BLOCKING
val btEdges = BitmapFactory.decodeStream(response.body?.byteStream())
return#async saveBitmapToAppStorage(btEdges, ImageType.EDGES)
}
val finalUri = this.async {
val urlFinal = URL(networkImageModel.finalImageUrl) // BLOCKING
val btFinal = BitmapFactory.decodeStream(urlFinal.openStream())
return#async saveBitmapToAppStorage(btFinal, ImageType.FINAL)
}
val labelUri = this.async {
val urlLabels = URL(networkImageModel.labelsImageUrl)
val btLabel = BitmapFactory.decodeStream(urlLabels.openStream())
return#async saveBitmapToAppStorage(btLabel, ImageType.LABELS)
}
awaitAll(edgesUri, finalUri, labelUri)
if(edgesUri.getCompleted() == null || finalUri.getCompleted() == null || labelUri.getCompleted() == null) {
return#withContext Result.failure(Exception("An image couldn't be saved"))
}
} catch (e: Exception) {
Result.failure<Long>(e)
}
try {
// Result.success( db.imageDao().insertImage(image))
Result.success(123) // A placeholder untill I actually get the URI's to create my Db Entity
} catch (e: Exception) {
Timber.e(e)
Result.failure(e)
}
}
}
//Save the bitmap and return Uri or null if failed
private fun saveBitmapToAppStorage(bitmap: Bitmap, imageType: ImageType): Uri? {
val type = when (imageType) {
ImageType.EDGES -> "edges"
ImageType.LABELS -> "labels"
ImageType.FINAL -> "final"
}
val filename = "img_" + System.currentTimeMillis().toString() + "_" + type
val file = File(context.filesDir, filename)
try {
val fos = file.outputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.close();
} catch (e: Exception) {
Timber.e(e)
return null
}
return file.toUri()
}
Here I am calling this function
viewModelScope.launch {
val imageID = appRepository.saveImageToDB(imageNetworkModel)
withContext(Dispatchers.Main) {
val uri = Uri.parse("$PAINT_DEEPLINK/$imageID")
navManager.navigate(uri)
}
}
Another issue I am facing is returning the URI in the first place and handling errors. As if one of these parts fails, I'd like to cancel the whole thing and return Result.failure(), but I am unsure on how to achieve that. As returning null just seems meh, I'd much prefer to have an error message or something along those lines.
Before the introduction of scoped storage i was using Download Manager to download pdf in my app and get the pdf from getExternalStorageDirectory, but due to scoped storage i can no longer use getExternalStorageDirectory as it is deprecated. I decided to move away from Download Manager as well as it downloads files in public directory and instead use retrofit to download pdf file.
I know i can use the requiredLegacyStorage tag in Android Manifest but it wont be applicable to Android 11 so i am not using that.
Here is my code
fun readAndDownloadFile(context: Context) {
readQuraanInterface?.downloadFile()
Coroutines.io {
file = File(context.filesDir,"$DESTINATION_DIRECTORY/$FILE_NAME$FILE_EXTENSION")
if (file?.exists() == true) {
renderPDF()
showPdf(mPageIndex, Direction.None)
} else {
Log.i("new","new0")
val response = readQuraanRepository.downloadPdf()
if (response.isSuccessful) {
Log.i("new","new00 ${file!!.path} ${response.body()?.byteStream().toString()}")
response.body()?.byteStream()?.let {
file!!.copyInputStreamToFile(
it
)
}
Log.i("new","new1")
// renderPDF()
// showPdf(mPageIndex, Direction.None)
} else {
Log.i("new","new2")
Coroutines.main {
response.errorBody()?.string()
?.let { readQuraanInterface?.downloadFailed(it) }
}
}
}
}
}
private fun File.copyInputStreamToFile(inputStream: InputStream) {
this.outputStream().use { fileOut ->
Log.i("new","new30")
inputStream.copyTo(fileOut)
}
}
Though the pdf id downloaded but the file is never stored using InputStream helper function which i have written. I need to add that pdf to my app's internal storage as well as render it which i am rendering using PDFRenderer.
You can use below code to download and save PDF using scoped storage. Here I am using Downloads directory. Don't forget to give required permissions.
#RequiresApi(Build.VERSION_CODES.Q)
fun downloadPdfWithMediaStore() {
CoroutineScope(Dispatchers.IO).launch {
try {
val url =
URL("https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf")
val connection: HttpURLConnection = url.openConnection() as HttpURLConnection
connection.requestMethod = "GET"
connection.doOutput = true
connection.connect()
val pdfInputStream: InputStream = connection.inputStream
val values = ContentValues().apply {
put(MediaStore.Downloads.DISPLAY_NAME, "test")
put(MediaStore.Downloads.MIME_TYPE, "application/pdf")
put(MediaStore.Downloads.IS_PENDING, 1)
}
val resolver = context.contentResolver
val collection =
MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val itemUri = resolver.insert(collection, values)
if (itemUri != null) {
resolver.openFileDescriptor(itemUri, "w").use { parcelFileDescriptor ->
ParcelFileDescriptor.AutoCloseOutputStream(parcelFileDescriptor)
.write(pdfInputStream.readBytes())
}
values.clear()
values.put(MediaStore.Downloads.IS_PENDING, 0)
resolver.update(itemUri, values, null, null)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
It is a more clean solution if you save file with Retrofit dynamic Urls.
Create Api
interface DownloadFileApi {
#Streaming
#GET
suspend fun downloadFile(#Url fileUrl: String): Response<ResponseBody>
}
And you can create the instance like
Retrofit.Builder()
.baseUrl("http://localhost/") /* We use dynamic URL (#Url) the base URL will be ignored */
.build()
.create(DownloadFileApi::class.java)
NOTE: You need to set a valid baseUrl even if you don't consume it since it is required by the retrofit builder
Save InputStream result in storage device (you can create a UseCase to do that)
class SaveInputStreamAsPdfFileOnDirectoryUseCase {
/**
* Create and save inputStream as a file in the indicated directory
* the inputStream to save will be a PDF file with random UUID as name
*/
suspend operator fun invoke(inputStream: InputStream, directory: File): File? {
var outputFile: File? = null
withContext(Dispatchers.IO) {
try {
val name = UUID.randomUUID().toString() + ".pdf"
val outputDir = File(directory, "outputPath")
outputFile = File(outputDir, name)
makeDirIfShould(outputDir)
val outputStream = FileOutputStream(outputFile, false)
inputStream.use { fileOut -> fileOut.copyTo(outputStream) }
outputStream.close()
} catch (e: IOException) {
// Something went wrong
}
}
return outputFile
}
private fun makeDirIfShould(outputDir: File) {
if (outputDir.exists().not()) {
outputDir.mkdirs()
}
}
}
Call the api and apply the use case :D
class DownloadFileRepository constructor(
private val service: DownloadFileApi,
private val saveInputStreamAsPdfFileOnDirectory: SaveInputStreamAsPdfFileOnDirectoryUseCase
) {
/**
* Download pdfUrl and save result as pdf file in the indicated directory
*
* #return Downloaded pdf file
*/
suspend fun downloadFileIn(pdfUrl: String, directory: File): File? {
val response = service.downloadFile(pdfUrl)
val responseBody = responseToBody(response)
return responseBody?.let { saveInputStreamAsFileOnDirectory(it.byteStream(), directory) }
}
fun responseToBody(response: Response<ResponseBody>): ResponseBody? {
if (response.isSuccessful.not() || response.code() in 400..599) {
return null
}
return response.body()
}
}
NOTE: You can use ContextCompat.getExternalFilesDirs(applicationContext, "documents").firstOrNull() as save directory
I am using the below code with targeted API 30 and after downloading its saving on the internal Download directory
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));//url=The download url of file
request.setMimeType(mimetype);
//------------------------COOKIE!!------------------------
String cookies = CookieManager.getInstance().getCookie(url);
request.addRequestHeader("cookie", cookies);
//------------------------COOKIE!!------------------------
request.addRequestHeader("User-Agent", userAgent);
request.setDescription("Qawmi Library Downloading");//Description
request.setTitle(pdfFileName);//pdfFileName=String Name of Pdf file
request.allowScanningByMediaScanner();
request.setAllowedOverMetered(true);
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
request.setDestinationInExternalPublicDir("/Qawmi Library"/*Custom directory name below api 29*/, pdfFileName);
} else {
//Higher then or equal api-29
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS,"/"+pdfFileName);
}
DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
dm.enqueue(request);
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 downloading file from server ,and saving to download folder in android ,accessing file path by following way to save
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
is depreceated in Android Q ,So I have to use Storage access framework .
fun downloadFile(url: String, originalFileName: String) {
mDownloadDisposable.add(
mRCloudRepository.downloadFile(url)
.flatMap(object : Function1<Response<ResponseBody>, Flowable<File>> {
override fun invoke(p1: Response<ResponseBody>): Flowable<File> {
return Flowable.create({
try {
val file = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absoluteFile, originalFileName)
val sink = file.sink().buffer()
sink.writeAll(p1.body()!!.source())
sink.close()
it.onNext(file)
it.onComplete()
} catch (e: IOException) {
e.printStackTrace()
it.onError(e)
}
}, BackpressureStrategy.BUFFER)
}
})
.subscribeOn(mIoScheduler)
.observeOn(mUiScheduler)
.subscribe(
{
cancelNotification()
val contentIntent: PendingIntent = PendingIntent.getActivity(mContext, NotificationBuilder.NOTIFICATION_DOWNLOAD_ID, getOpenFileIntent(it), PendingIntent.FLAG_CANCEL_CURRENT)
NotificationBuilder.showDownloadedNotification(mContext!!, 2, "Downloaded $originalFileName ", "", contentIntent)
}, {
cancelNotification()
it.printStackTrace()
}
)
)
}
You can try DocumentFileCompat#createDownloadWithMediaStoreFallback() in Simple Storage:
val uri = DocumentFileCompat.createDownloadWithMediaStoreFallback(applicationContext, FileDescription(filename))
val output = uri?.openOutputStream(applicationContext)
val input = // get input stream from Response<ResponseBody>
// then, write input stream to output stream
In Android 10, accessing files in Download directory requires URI, instead of direct file path.