android - download base64 encoded pdf file and open it - android

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"

Related

Get Uri of recorded video CameraX

I am recording a video with the camerax library, I would like to retrieve the uri of the recorded file to open it with InputStream but it keeps giving me FILENOTFOUND
This is the code I use to start recording the video
contentValues = ContentValues().apply {
put(MediaStore.Video.Media.DISPLAY_NAME, currentFile!!.name)
put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4")
put(MediaStore.MediaColumns.RELATIVE_PATH, "Movies/" + DataHolder.getInstance().albumName)
put(MediaStore.MediaColumns.DATE_TAKEN, System.currentTimeMillis())
}
requireContext().contentResolver.run {
val mediaStoreOutput = MediaStoreOutputOptions.Builder(
requireActivity().contentResolver,
MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
.setContentValues(contentValues)
.build()
currentRecording = videoCapture.output.prepareRecording(requireActivity(), mediaStoreOutput)
.apply { withAudioEnabled() }
.start(mainThreadExecutor, captureListener)
val resolver = requireContext().contentResolver
currentUri = resolver.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, contentValues)
And this is when I try to initialize InputStream:
try {
val resolver: ContentResolver = requireContext().getContentResolver()
val fis: InputStream? = resolver.openInputStream(currentUri!!)
}
catch (e: Exception) {
Log.e("asd", "Error File not found")
}
Try using this path instead:
val path = app.externalMediaDirs[0].absolutePath
val dir = File(path, "/videos")
if (!dir.exists()) {
val success = dir.mkdirs()
if (!success) {
return
// error/no file found
}
}
val file = File(dir , name)
It has become tricky in the new sdk versions to write to storage
In this way you are placing the video in a directory you can later fetch it from

How to save Document file in external storage after android API LEVEL28 With Android SAF(Storage Access Framework))

This Code Works Fine With Media Files I want a solution For Document Files
I Don't Know how to put contentValues For Document Files
fun getFile(fileName: String): File? {
with(sharePrefHelper.app){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val values = ContentValues()
// Here is My Question That what should i Do Here Because this is for document not for image
values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
// for MIME_TYPE "image/jpg" this is working
values.put(MediaStore.Images.Media.MIME_TYPE, "text/csv")
values.put(MediaStore.MediaColumns.RELATIVE_PATH, "DCIM/Donny")
contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)?.let {
it.path?.let { finalPath ->
return File(finalPath)
}
}
} else {
val directory: File = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM + "/Donny")
if (!directory.exists()){
directory.mkdirs()
}
return File(directory, fileName)
}
return null
}
}
This Code Works Fine with media Files
My Question Here is How to save documents like CSV File in outer folder of android device
EDIT :
Well well well, I'm still trying to add anytype of file in the "download" directory.
Personnaly, I'm trying to copy a file from my assetFolder and paste it to the "Download" folder. I haven't succeeded yet.
However, I can currently CREATE anytype of file in that folder, it's working with the method below. I hope this can help you.
Here is my code :
public void saveFileToPhone(InputStream inputStream, String filename) {
OutputStream outputStream;
Context myContext = requireContext();
try {
if(Build.VERSION.SDK_INT >=Build.VERSION_CODES.Q){
ContentResolver contentResolver = requireContext().getContentResolver();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.Downloads.DISPLAY_NAME,filename);
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);
Uri collection = MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
Uri fileUri = contentResolver.insert(collection, contentValues);
outputStream = contentResolver.openOutputStream(Objects.requireNonNull(fileUri));
Objects.requireNonNull(outputStream);
}
}catch (FileNotFoundException e) {
e.printStackTrace();
}
}
Here it is what i have done with clean way
This is file provider activity
class FileProviderActivity : AppCompatActivity() {
var commonIntentLauncher: ActivityResultLauncher<Intent?> = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
result.data?.let {
intent.getParcelableExtra<ResultReceiver>("FileReceiver")?.send(0, bundleOf(
"FileUri" to result?.data?.data.toString()
))
finish()
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
val activityIntent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = intent.getStringExtra("fileType")
putExtra(Intent.EXTRA_TITLE, intent.getStringExtra("fileName"))
putExtra(DocumentsContract.EXTRA_INITIAL_URI, MediaStore.Downloads.EXTERNAL_CONTENT_URI)
}
commonIntentLauncher.launch(activityIntent)
}else{
val directory: File = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS + "/Dunny/CSVFiles")
if (!directory.exists()){
directory.mkdirs()
}
intent.getParcelableExtra<ResultReceiver>("FileReceiver")?.send(0, bundleOf(
"FileUri" to File(directory, intent.getStringExtra("fileName")!!).toURI().toString()
))
finish()
}
}
}
This FileProviderHelper
class MyFileProvider {
companion object {
fun with(context: Context) = FileRequest(context)
}
class FileRequest(private val context: Context) {
fun request(fileName: String, fileType: String = "application/*", file: (Uri?) -> Unit ) {
val intent = Intent(context, FileProviderActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra("fileName",fileName)
.putExtra("fileType", fileType)
.putExtras(bundleOf("FileReceiver" to FileReceiver(file)))
context.startActivity(intent)
}
}
internal class FileReceiver(private val file: (Uri?) -> Unit) : ResultReceiver(Handler(Looper.getMainLooper())) {
override fun onReceiveResult(resultCode: Int, resultData: Bundle?) {
super.onReceiveResult(resultCode, resultData)
resultData?.let {
file(it.getString("FileUri")?.toUri())
}
}
}
}
Here Is Use Of this Function
MyFileProvider.with(this).request("TestFile.csv","application/*") { fileUri ->
toast(fileUri.toString())
}

Pdf intent not showing file

I'm trying to display in the pdf intent of android an file using his Uri, but it seems that I always get an blank pdf and don't know why, can someone give me some advice why.
Code:
private fun pdfConverter(pdfFile: DtoSymptomCheckerPdf?) {
val documentsPath = File(context?.filesDir, "documents")
if(!documentsPath.exists()){
documentsPath.mkdir()
}
val file = File(documentsPath, pdfFile?.filename)
val pdfAsBytes: ByteArray = android.util.Base64.decode(pdfFile?.file, 0)
val os = FileOutputStream(file,false)
os.write(pdfAsBytes)
os.flush()
os.close()
println("EL CONTENT: " + file.length())
val uri = FileProvider.getUriForFile(App.applicationContext,
BuildConfig.APPLICATION_ID + ".provider", file)
val pdfIntent = Intent(Intent.ACTION_VIEW)
pdfIntent.setDataAndType(uri, "application/pdf")
pdfIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
try {
startActivity(pdfIntent)
} catch (e: ActivityNotFoundException) {
Toast.makeText(requireContext(), "Can't read pdf file",
Toast.LENGTH_SHORT).show()
}
}

android Writing to internal storage is not supported

I am try to take photo in private folder and save to public media store.
val takePictureContract = registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
if (success) {
uri?.let { fileUri ->
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, "pic_${System.currentTimeMillis()}")
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
}
val mediaUri = contentResolver.insert(
MediaStore.Images.Media.INTERNAL_CONTENT_URI,
contentValues
)
mediaUri?.let { mUri ->
contentResolver.openOutputStream(mUri)?.use { os ->
contentResolver.openInputStream(fileUri)?.use { inputStream ->
inputStream.copyTo(os)
}
}
}
}
}
}
fun takePic() {
val currentTimeMillis = System.currentTimeMillis()
val folder = File(filesDir, "images")
val file = File(folder, "pic_${currentTimeMillis}")
folder.mkdirs()
uri = FileProvider.getUriForFile(this, "${packageName}.provider", file)
takePictureContract.launch(uri)
}
But after take photo, I have this error message. What's wrong with my code?
Error message I get is :
java.lang.UnsupportedOperationException: Writing to internal storage is not supported
If change INTERNAL_CONTENT_URI to EXTERNAL_CONTENT_URI, and require permission WRITE_EXTERNAL_STORAGE, I can save photo success.

why my recorded video become 0 bytes after uploaded to server using retrofit?

my app tries to capture a video, and then the result will be sent to server
so I capture the video using the code below, but when I upload it to server, the file size is always 0 byte
because I create it in temporary file ?
here is the code to record a video
private fun recordVideo() {
val videoFile = createVideoFile()
videoFile?.let {
videoUri = FileProvider.getUriForFile(mContext,"com.video.record.fileprovider",it)
val intent = Intent(MediaStore.ACTION_VIDEO_CAPTURE)
intent.putExtra(MediaStore.EXTRA_OUTPUT,videoUri)
startActivityForResult(intent,REQUEST_VIDEO_CAPTURE)
}
}
private fun createVideoFile() : File? {
val fileName = "myVideoAndroid"
val storageDir = mActivity.getExternalFilesDir(Environment.DIRECTORY_MOVIES)
return File.createTempFile(fileName,".mp4",storageDir) // <---- because I make it in temporary file ?
}
and here is the provider tag in the manifest
<provider
android:authorities="com.video.record.fileprovider"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
and here is the file path xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android = "http://schemas.android.com/apk/res/android">
<external-path
name="my_videos"
path="Android/data/com.xxxx.mediauploader/files/Movies"/>
</paths>
the video seems good before uploading to server, I can play it perfectly.
and then I upload it using retrofit by using the code below
the interface
#Multipart
#POST("uploadvideo")
fun uploadVideo(
#Part video: MultipartBody.Part
): Call<UploadResponse>
and then used it like this
private fun createVideoFile() : File? {
val fileName = "myVideoAndroid"
val storageDir = mActivity.getExternalFilesDir(Environment.DIRECTORY_MOVIES)
return File.createTempFile(fileName,".mp4",storageDir)
}
val file = File(createVideoFile()!!.absolutePath)
val fileVideoPart = MultipartBody.Part.createFormData("videoFile", file.name, RequestBody.create("video/*".toMediaTypeOrNull(), file))
val call = uploadMediaAPI.uploadVideo(video = fileVideoPart)
call.enqueue(object : Callback<UploadResponse> {
})
I can upload it successfully, but the result is always 0 bytes in server
how to solve this ?
I finally can solve this problem, maybe because the videoUri is created in temporary file, so we need to convert it to real file
pass this videoFile to your retrofit
val videoFile = fileFromContentUri(mContext,videoUri) // <--- pass your temporary videoUri here
the code for fileFromContentUri will be like this
fun fileFromContentUri(context: Context, contentUri: Uri): File {
// Preparing Temp file name
val fileExtension = getFileExtension(context, contentUri)
val fileName = "temp_file" + if (fileExtension != null) ".$fileExtension" else ""
// Creating Temp file
val tempFile = File(context.cacheDir, fileName)
tempFile.createNewFile()
try {
val oStream = FileOutputStream(tempFile)
val inputStream = context.contentResolver.openInputStream(contentUri)
inputStream?.let {
copy(inputStream, oStream)
}
oStream.flush()
} catch (e: Exception) {
e.printStackTrace()
}
return tempFile
}
private fun getFileExtension(context: Context, uri: Uri): String? {
val fileType: String? = context.contentResolver.getType(uri)
return MimeTypeMap.getSingleton().getExtensionFromMimeType(fileType)
}
#Throws(IOException::class)
private fun copy(source: InputStream, target: OutputStream) {
val buf = ByteArray(8192)
var length: Int
while (source.read(buf).also { length = it } > 0) {
target.write(buf, 0, length)
}
}

Categories

Resources