Downloading file and saving it to download folder using kotlin in Android - android

I am trying to download an apk file from the web server and store it in the download folder. I am using fuel library
Fuel.download("https://mypath/app.apk").destination {
response, url -> val dir = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString()) File(dir, "abcd.apk")
}.progress {
readBytes, totalBytes -> val progress = readBytes.toFloat() / totalBytes.toFloat()
Log.d("log",progress.toString())
}.response {
req, res, result -> Log.d("log","download completed ")
Log.d("log",Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString() + "/abcd.apk")
}
I am getting bytesArray in response but the file is not saving to download folder. I have write_external_storage on my Manifest file. Please let me know what am I missing?

As you mentioned in comment that you are using targetSdkVersion as 23. Beginning with Android 6.0 (API level 23), if you set targetSdkVersion as 23 in your app you have to implement rumtime permission in your app.
To implement it please refer official documentation here Requesting Permissions at Run Time

fun storeImage(link: String) : String {
val policy: StrictMode.ThreadPolicy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
val cw = ContextWrapper(applicationContext)
val directory: File = cw.getDir("imageDir", Context.MODE_PRIVATE)
val pictureFile = File(directory, "backgroundImage.gif")
URL(link).openStream().use { input ->
FileOutputStream(pictureFile).use { output ->
input.copyTo(output)
Log.e("DD GIF", "storeImage: FILE SAVED" )
return "SAVED"
}
}
}
it is working for me.

Related

How to write a file in android shared internal/external storage?

I am currently working with one of my app updates and I am looking for a way to save some files at the root of shared internal storage.
Don't confuse with the word shared here. I just meant here with the phone's internal/external storage which holds a large amount of data.
Now coming to the main point, I have an app that uses the FFmpeg library for android and it records the live streams and saves it into the phone's storage.
Now here's the problem I don't want to save this file in my app package folder.
e.g: /storage/emulated/0/Android/data/com.app.package/files/...
I want to save this video file in the root of the storage folder where I can create a special folder for my app just like WhatsApp then save all required data in it only.
e.g: /storage/emulated/0/MyApp/...
To be more concise I want my app just like WhatsApp which has a separate folder in internal storage for storing its app-related data.
Now, so far I tried these.
getExternalFilesDir(null)?.absolutePath -> /storage/emulated/0/Android/data/com.app.package/files
getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)?.absolutePath -> /storage/emulated/0/Android/data/com.app.package/files/Download
externalCacheDir?.absolutePath -> /storage/emulated/0/Android/data/com.app.package/cache
filesDir?.absolutePath -> /data/user/0/com.app.package/files
Environment.getRootDirectory().absolutePath -> /system
Environment.getExternalStorageDirectory().absolutePath -> /storage/emulated/0
Environment.getExternalStoragePublicDirectory("").absolutePath -> /storage/emulated/0
Now these to methods can work for me but they are deprecated getExternalStorageDirectory() & getExternalStoragePublicDirectory()
Just keep in mind my solution is related to the WhatsApp storage folder.
I searched a lot but not so much help as I don't want to go with just a copy-paste and hack solutions. I want to do it in a clean way.
I am trying to open a File Picker via intent for this I have set permission
....
....
and for intent.
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.type = "*/*"
intent.flags = Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
startActivityForResult(Intent.createChooser(intent, "Select .... file"), RC)
Now here on Android 10, a file picker is not opening and this works perfectly if I add the requestLegacyExternalStorage flag in the manifest.
Or
if I set android:maxSdkVersion="29" also worked but here google wants to use Some SAF or Media Store API but I don't get it I just want to pick a file just a simple txt nothing else.
You can save video file to external storage using this:
#RequiresApi(api = Build.VERSION_CODES.Q)
#Throws(FileNotFoundException::class)
fun addToApiAbove29Gallery(
context: Context,
file: File?,
fileName: String?,
destinationPath: String?,
action: () -> Unit
) {
val valuesvideos: ContentValues
valuesvideos = ContentValues()
valuesvideos.put(
MediaStore.Video.Media.RELATIVE_PATH, Environment.DIRECTORY_DCIM
+ File.separator + context.resources.getString(R.string.app_name)
+ File.separator + File(destinationPath).name
)
valuesvideos.put(MediaStore.Video.Media.TITLE, "${fileName}.mp4")
valuesvideos.put(MediaStore.Video.Media.DISPLAY_NAME, "${fileName}.mp4")
valuesvideos.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4")
valuesvideos.put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis() / 1000)
valuesvideos.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis())
valuesvideos.put(MediaStore.Video.Media.IS_PENDING, 1)
val resolver = context.contentResolver
val collection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val uriSavedVideo = resolver.insert(collection, valuesvideos)
val pfd = context.contentResolver.openFileDescriptor(uriSavedVideo!!, "w")
val executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())
val handler = Handler(Looper.getMainLooper())
executor.execute {
if (pfd != null) {
try {
val out =
FileOutputStream(pfd.fileDescriptor)
val inputStream = FileInputStream(file)
val buf = ByteArray(8192)
var len: Int
while (inputStream.read(buf).also { len = it } > 0) {
out.write(buf, 0, len)
}
out.close()
inputStream.close()
pfd.close()
valuesvideos.clear()
valuesvideos.put(MediaStore.Video.Media.IS_PENDING, 0)
try {
setExifFromUri(
context,
uriSavedVideo,
File(destinationPath)
)
} catch (e: IOException) {
e.printStackTrace()
}
} catch (e: Exception) {
e.printStackTrace()
}
handler.post {
context.contentResolver.update(uriSavedVideo, valuesvideos, null, null)
action()
}
}
}
}
Note that this code is only for android 10 and above, because you can easily save the video file to ecternal storage below android 10.

DownloadManager works for API 30 but no for API 27

I am trying to download a pdf file from the server (I have the url), and I am trying this solution
fun downloadPDF(url: String?, fileName: String): ResponseStatus {
val uri = Uri.parse(url)
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager?
val request = DownloadManager.Request(uri)
request.setAllowedNetworkTypes(
DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE
)
request.setTitle(fileName)
request.setDescription("Android Data download using DownloadManager.")
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
request.setMimeType("*/*")
if (downloadManager == null) {
return ResponseStatus.Error( context.getString(R.string.download_error) )
}
downloadManager.enqueue(request)
return ResponseStatus.OK(uri)
}
this works for API 30 but not for API 27 and I don't know why.
Can someone help me please?
UPDATE:
I think that this can be a permissions problem because I tried again in API 27 and see this in the log java.lang.SecurityException: No permission to write to /storage/emulated/0/Download/file name12-34: Neither user 10080 nor current process has android.permission.WRITE_EXTERNAL_STORAGE.
I was able to solve the problem, I changed this line
request.setDestinationInExternalPublicDir (Environment.DIRECTORY_DOWNLOADS, fileName) for a uri that I parse with the address of the download folder and the file name, and I set it with request.setDestinationUri (destinationUri)
val destination = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString() +
"/" + fileName
val destinationUri = Uri.parse("$FILE_BASE_PATH$destination")

FileObserver not working on Android SDK 30

I am working on an android application where i have used FileObserver to get data changes on a specific folder. The folder is WhatsApp Images and whenever there is a new file added to that folder i perform my further work when onEvent of FileObserver is triggered.
Everything works fine until i set targetSdkVersion=29 but as i am migrating my project to targetSdkVersion=30 FileObserver stopped working.
Below is my code for FileObserver
import android.os.Environment
import android.os.FileObserver
import android.util.Log
import java.io.File
class WhatsAppImageObserver11(var notify:(msg:String)->Unit) : FileObserver(
File(
Environment.getExternalStorageDirectory().toString(),
Constants.whatsapp_images_path11).toString(), ALL_EVENTS
) {
init {
Log.d("WhatsAppImageObserver11", "start")
}
override fun onEvent(i: Int, str: String?) {
val str2 = "WhatsAppImageObserver11"
if (i == CREATE || i == MOVED_TO && str != ".probe") {
val sb = StringBuilder()
sb.append("create File path--> ")
sb.append(str)
Log.d(str2, sb.toString())
try {
val whatsDeleted = File(
Environment.getExternalStorageDirectory().path,
Constants.whatsapp_reserved_media
)
if(!whatsDeleted.exists()) {
whatsDeleted.mkdirs()
}
val srcFile = File(
Environment.getExternalStorageDirectory(),
Constants.whatsapp_images_path11+str)
val destFile = File(Environment.getExternalStorageDirectory(), Constants.whatsapp_reserved_media+str)
if (srcFile.exists()){
srcFile.copyTo(target = destFile, overwrite = false, bufferSize = DEFAULT_BUFFER_SIZE)
}
} catch (e: Exception) {
val sb2 = StringBuilder()
sb2.append("create error: ")
sb2.append(e.toString())
Log.d(str2, sb2.toString())
}
}
if (i and 512 != 0 || i and 1024 != 0) {
val sb3 = StringBuilder()
sb3.append("dlete File path--> ")
sb3.append(str)
Log.d(str2, sb3.toString())
try {
val whatsDeleted = File(
Environment.getExternalStorageDirectory().path,
Constants.new_whatsapp_deleted_media
)
if(!whatsDeleted.exists()) {
whatsDeleted.mkdirs()
}
val srcFile = File(Environment.getExternalStorageDirectory().path, Constants.whatsapp_reserved_media+str)
val destFile = File(Environment.getExternalStorageDirectory().path, Constants.new_whatsapp_deleted_media+str)
if (srcFile.exists()){
srcFile.copyTo(target = destFile, overwrite = false, bufferSize = DEFAULT_BUFFER_SIZE)
srcFile.delete()
notify(destFile.absolutePath)
}
} catch (e2: Exception) {
val sb4 = StringBuilder()
sb4.append("del error: ")
sb4.append(e2.toString())
Log.d(str2, sb4.toString())
}
}
}
}
Here in this FileObserver i am copying media from WhatsApp Reserved Media to My own WhatsAppDeleted folder for media recovery feature.
What i have tried?
1- As i know Environment.getExternalStorageDirectory() is deprecated i tried replacing it with mContext.getExternalFilesDir(null).getAbsolutePath()
2- Also checked using android:preserveLegacyExternalStorage="true" in Manifest.
3- Tried added ignore attribute in
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage"/>
What is Required?
1- FileObserver on path "/Android/media/" + WHATSAPP_PKG_NAME + "/WhatsApp/Media/WhatsApp Images/" should trigger onEvent when i set targetSdkVersion-30
Can somebody please help me out with this? Any help will be appreciated.
Thank you
As written here:
Apps that run on Android 11 but target Android 10 (API level 29) can still request the requestLegacyExternalStorage attribute. This flag allows apps to temporarily opt out of the changes associated with scoped storage, such as granting access to different directories and different types of media files. After you update your app to target Android 11, the system ignores the requestLegacyExternalStorage flag.
Basically, you don't have permission to "listen" to external directories in the device, but only to your own internal/external directories.
You should use the MediaStore API in order to access media files which have been scanned by the OS (Including WhatsApp's files).
You can use components like JobService to register for the event of a new media been scanned.
Enjoy

make a file shareable using drive api in android studio

In my mobile application I am using Drive API to upload an file to the google drive. It works well. But it uploads all the files as not shareable. I want to make this shareable.
Here is my code in kotlin
// upload a file in to google drive
fun uploadFile(localFile: java.io.File, mimeType: String?, folderId: String?): Task<Any>? {
return Tasks.call(mExecutor, Callable<Any> { // Retrieve the metadata as a File object.
val root: List<String> = folderId?.let { listOf(it) } ?: listOf("root")
val metadata = File()
.setParents(root)
.setMimeType(mimeType)
.setName(localFile.name)
val fileContent = FileContent(mimeType, localFile)
val fileMeta = mDriveService.files().create(
metadata,
fileContent
).execute()
val googleDriveFileHolder = GoogleDriveFileHolder()
googleDriveFileHolder.setId(fileMeta.id)
googleDriveFileHolder.setName(fileMeta.name)
googleDriveFileHolder
})
}
There is a answer in js here but I dont understand how to do it inside my function. Please help.

How can we access an expansion file in Android Q?

I'm writing an app that needs an expansion file and I want to ensure it will be compatible with Android Q. It seems the documentation provided does not address the changes in Android Q. In Android Q, getExternalStorageDirectory() won't be able to be used so how can we access the expansion file?
From the documentation linked to in the question, we know that an expansion file's name has the form:
[main|patch].<expansion-version>.<package-name>.obb
and the getObbDir() method returns the specific location for expansion files in the following form:
<shared-storage>/Android/obb/<package-name>/
So, the question is how do we access such files?
To answer this question, I have taken a directory containing five APK files and created an OBB file named "main.314159.com.example.opaquebinaryblob.obb" using JOBB. My intention is to mount and read this OBB file to display the APK file names and the count of entries in each APK (read as Zip files) in a small demo app.
The demo app will also try to create/read test files in various directories under the external storage directory.
The following was performed on a Pixel XL emulator running the latest available version of "Q" (Android 10.0 (Google APIs)). The app has the following characterisics:
targetSdkVersion 29
minSdkVersion 18
No explicit permissions
specified in the manifest
I peeked ahead to see what directory getObbDir() returns for this little app and found that it is
/storage/emulated/0/Android/obb/com.example.opaquebinaryblob
so I uploaded my OBB file to
/storage/emulated/0/Android/obb/com.example.opaquebinaryblob/main.314159.com.example.opaquebinaryblob.obb
using Android Studio. Here is where the file wound up.
So, can we mount and read this OBB file? Can we create/read files in other directories within the external files path? Here is what the app reports on API 29:
The only files that are accessible reside in /storage/emulated/0/Android/obb/com.example.opaquebinaryblob. Other files in the hierarchy cannot be either created or read. (Interestingly, though, the existence of these files could be determined.)
For the preceding display, the app opens the OBB file and reads it directly without mounting it.
When we try to mount the OBB file and dump its contents, this is what is reported:
Which is what we expect. In short, it looks like Android Q is restricting access to the external files directory while allowing targeted access based up the package name of the app.
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var myObbFile: File
private lateinit var mStorageManager: StorageManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
obbDumpText.movementMethod = ScrollingMovementMethod()
val sb = StringBuilder()
val extStorageDir = Environment.getExternalStorageDirectory()
sb.appendln("getExternalStorageDirectory() reported at $extStorageDir").appendln()
myObbFile = File(obbDir, BLOB_FILE_NAME)
val obbDir = obbDir
sb.appendln("obbDir reported at $obbDir").appendln()
myObbFile = File(obbDir, BLOB_FILE_NAME)
val directoryPathList = listOf(
"$extStorageDir",
"$extStorageDir/Pictures",
"$extStorageDir/Android/obb/com.example.anotherpackage",
"$extStorageDir/Android/obb/$packageName"
)
var e: Exception?
for (directoryPath in directoryPathList) {
val fileToCheck = File(directoryPath, TEST_FILE_NAME)
e = checkFileReadability(fileToCheck)
if (e == null) {
sb.appendln("$fileToCheck is accessible.").appendln()
} else {
sb.appendln(e.message)
try {
sb.appendln("Trying to create $fileToCheck")
fileToCheck.createNewFile()
sb.appendln("Created $fileToCheck")
e = checkFileReadability(fileToCheck)
if (e == null) {
sb.appendln("$fileToCheck is accessible").appendln()
} else {
sb.appendln("e").appendln()
}
} catch (e: Exception) {
sb.appendln("Could not create $fileToCheck").appendln(e).appendln()
}
}
}
if (!myObbFile.exists()) {
sb.appendln("OBB file doesn't exist: $myObbFile").appendln()
obbDumpText.text = sb.toString()
return
}
e = checkFileReadability(myObbFile)
if (e != null) {
// Need to request READ_EXTERNAL_STORAGE permission before reading OBB file
sb.appendln("Need READ_EXTERNAL_STORAGE permission.").appendln()
obbDumpText.text = sb.toString()
return
}
sb.appendln("OBB is accessible at")
.appendln(myObbFile).appendln()
mStorageManager = getSystemService(Context.STORAGE_SERVICE) as StorageManager
obbDumpText.text = sb.toString()
}
private fun dumpMountedObb(obbMountPath: String) {
val obbFile = File(obbMountPath)
val sb = StringBuilder().appendln("Dumping OBB...").appendln()
sb.appendln("OBB file path is $myObbFile").appendln()
sb.appendln("OBB mounted at $obbMountPath").appendln()
val listFiles = obbFile.listFiles()
if (listFiles == null || listFiles.isEmpty()) {
Log.d(TAG, "No files in obb!")
return
}
sb.appendln("Contents of OBB").appendln()
for (listFile in listFiles) {
val zipFile = ZipFile(listFile)
sb.appendln("${listFile.name} has ${zipFile.entries().toList().size} entries.")
.appendln()
}
obbDumpText.text = sb.toString()
}
private fun checkFileReadability(file: File): Exception? {
if (!file.exists()) {
return IOException("$file does not exist")
}
var inputStream: FileInputStream? = null
try {
inputStream = FileInputStream(file).also { input ->
input.read()
}
} catch (e: IOException) {
return e
} finally {
inputStream?.close()
}
return null
}
fun onClick(view: View) {
mStorageManager.mountObb(
myObbFile.absolutePath,
null,
object : OnObbStateChangeListener() {
override fun onObbStateChange(path: String, state: Int) {
super.onObbStateChange(path, state)
val mountPath = mStorageManager.getMountedObbPath(myObbFile.absolutePath)
dumpMountedObb(mountPath)
}
}
)
}
companion object {
const val BLOB_FILE_NAME = "main.314159.com.example.opaquebinaryblob.obb"
const val TEST_FILE_NAME = "TestFile.txt"
const val TAG = "MainActivity"
}
}
activity_main.xml
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="16dp"
tools:context=".MainActivity">
<TextView
android:id="#+id/obbDumpText"
android:layout_width="0dp"
android:layout_height="0dp"
android:scrollbars="vertical"
android:text="Click the button to view content of the OBB."
android:textColor="#android:color/black"
app:layout_constraintBottom_toTopOf="#+id/dumpMountObb"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="spread_inside" />
<Button
android:id="#+id/dumpMountObb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="Dump\nMounted OBB"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/obbDumpText"
app:layout_constraintVertical_bias="0.79" />
</androidx.constraintlayout.widget.ConstraintLayout>
For a follow-up as stated here:
Since Android 4.4 (API level 19), apps can read OBB expansion files without external storage permission. However, some implementations of Android 6.0 (API level 23) and later still require permission, so you will need to declare the READ_EXTERNAL_STORAGE permission in the app manifest and ask for permission at runtime...
Does this apply to Android Q? It is not clear. The demo shows that it does not for the emulator. I hope that this is something that will be consistent across devices.

Categories

Resources