What's "PackageInstaller" class on Lollipop, and how to use it? - android

Background
I've noticed there is a new function on the PackageManager called "getPackageInstaller" , with minAPI 21 (Lollipop).
I've reached the "PackageInstaller" class, and this is what it is written about it:
Offers the ability to install, upgrade, and remove applications on the
device. This includes support for apps packaged either as a single
"monolithic" APK, or apps packaged as multiple "split" APKs.
An app is delivered for installation through a
PackageInstaller.Session, which any app can create. Once the session
is created, the installer can stream one or more APKs into place until
it decides to either commit or destroy the session. Committing may
require user intervention to complete the installation.
Sessions can install brand new apps, upgrade existing apps, or add new
splits into an existing app.
Questions
What is this class used for? Is it even available for third party apps (I don't see any mentioning of this) ?
Can it really install apps?
Does it do it in the background?
What are the restrictions?
Does it require permissions? If so, which?
Is there any tutorial of how to use it?

OK, I've found some answers:
Can be used for installing/updating APK files, including split-APK files. Maybe even more.
Yes, but the user will need to confirm, one app after another.
Maybe if the app is built in.
Seems it requires to read the entire APK file/s before requesting the user to install.
Needs permission REQUEST_INSTALL_PACKAGES
Haven't found any, but someone showed me here how to install split-apk files, and here's how to do it for a single file using SAF, with and without PackageInstaller. Note that this is just a sample. I don't think it's a good practice to do it all on the UI thread.
manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
package="com.android.apkinstalltest">
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<application tools:ignore="AllowBackup,GoogleAppIndexingWarning"
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:theme="#style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<service android:name=".APKInstallService"/>
</application>
</manifest>
APKInstallService
class APKInstallService : Service() {
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
when (intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE)) {
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
Log.d("AppLog", "Requesting user confirmation for installation")
val confirmationIntent = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
confirmationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
try {
startActivity(confirmationIntent)
} catch (e: Exception) {
}
}
PackageInstaller.STATUS_SUCCESS -> Log.d("AppLog", "Installation succeed")
else -> Log.d("AppLog", "Installation failed")
}
stopSelf()
return START_NOT_STICKY
}
override fun onBind(intent: Intent) = null
}
MainActivity
class MainActivity : AppCompatActivity() {
private lateinit var packageInstaller: PackageInstaller
#TargetApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
packageInstaller = packageManager.packageInstaller
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "application/vnd.android.package-archive"
startActivityForResult(intent, 1)
}
// override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
// super.onActivityResult(requestCode, resultCode, resultData)
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && requestCode == 1 && resultCode == Activity.RESULT_OK && resultData != null) {
// val uri = resultData.data
// grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
// val intent = Intent(Intent.ACTION_INSTALL_PACKAGE)//
// .setDataAndType(uri, "application/vnd.android.package-archive")
// .putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
// .putExtra(Intent.EXTRA_RETURN_RESULT, false)
// .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
// startActivity(intent)
// }
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
super.onActivityResult(requestCode, resultCode, resultData)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && requestCode == 1 && resultCode == Activity.RESULT_OK && resultData != null) {
val uri = resultData.data ?: return
grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
val installParams = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
var cursor: Cursor? = null
var outputStream: OutputStream? = null
var inputStream: InputStream? = null
var session: PackageInstaller.Session? = null
try {
cursor = contentResolver.query(uri, null, null, null, null)
if (cursor != null) {
cursor.moveToNext()
val fileSize = cursor.getLong(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_SIZE))
val fileName = cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME))
installParams.setSize(fileSize)
cursor.close()
val sessionId = packageInstaller.createSession(installParams)
Log.d("AppLog", "Success: created install session [$sessionId] for file $fileName")
session = packageInstaller.openSession(sessionId)
outputStream = session.openWrite(System.currentTimeMillis().toString(), 0, fileSize)
inputStream = contentResolver.openInputStream(uri)
inputStream.copyTo(outputStream)
session.fsync(outputStream)
outputStream.close()
outputStream = null
inputStream.close()
inputStream = null
Log.d("AppLog", "Success: streamed $fileSize bytes")
val callbackIntent = Intent(applicationContext, APKInstallService::class.java)
val pendingIntent = PendingIntent.getService(applicationContext, 0, callbackIntent, 0)
session!!.commit(pendingIntent.intentSender)
session.close()
session = null
Log.d("AppLog", "install request sent. sessions:" + packageInstaller.mySessions)
}
} catch (e: Exception) {
Log.d("AppLog", "error:$e")
} finally {
outputStream?.close()
inputStream?.close()
session?.close()
cursor?.close()
}
}
}
}

Related

How do i create and fill a file in the directory the User choose with ACTION_OPEN_DOCUMENT_TREE on Android 9-11

I try to make an Export Function for my App, that can export(and later Import) data to a txt-file in the shared storage. I use the ACTION_OPEN_DOCUMENT_TREE to choose a Folder and get "resultdata: Intent?" back.
For explanation in the Downloads-folder/Documents-Folder there is a "myApp"-folder. The user gave permission for that folder. So i get an Intent? with the path to this place back
How can i use that to create a "Spells.txt" in said folder without ACTION_CREATE_DOCUMENT
Edit: Thanks to blackapps, I've found DocumentFile, which helped create and fill the File. Here are the relevant parts of my Code so far:
lateinit var permissedFolder: Intent
in onCreate
permissiontest_btn_Choose_Permission.setOnClickListener(){chooseFolder()}
permissiontest_btn_createFile.setOnClickListener(){createFile()}
fun chooseFolder(){
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
}
startActivityForResult(intent, 100)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
super.onActivityResult(requestCode, resultCode, resultData)
if(requestCode==100 && resultCode== RESULT_OK){
if (resultData != null) {
permissedFolder = resultData
}
fun createFile() {
if(permissedFolder.data != null) {
val folderUri:Uri = permissedFolder.data!!
val enabledDirectory:DocumentFile= DocumentFile.fromTreeUri(this, folderUri)!!
val spellsExportDF =enabledDirectory.createFile(".txt","Spells.txt")
val spellsExportURI = spellsExportDF?.uri
if(spellsExportURI != null) {
val fileOutputStream = getContentResolver().openOutputStream(spellsExportURI)
//FileOutputStream(spellsExportURI, true)
fileOutputStream.use { fileout ->
fileout?.writer(Charsets.UTF_8)?.use {
it.write(test)
it.flush()
it.close()
}
}
val readfile = spellsExportDF.canRead()
val writefile =spellsExportDF.canWrite()
Toast.makeText(this, "can Read: $readfile", Toast.LENGTH_SHORT).show()
Toast.makeText(this, "can Write: $writefile", Toast.LENGTH_SHORT).show()
}

No persistable permission grants found for UID 10208 [ Security Exception ]

I am using Storage Access Framework for Image Picker in my app. Below is the code
val types = arrayOf("image/png", "image/jpeg", "image/jpg")
val intent = Intents.createDocumentIntent(types, true)
if (canDeviceHandle(intent)) caller.startActivityForResult(intent, OPEN_GALLERY)
Here is the intent for creating document
fun createDocumentIntent(types: Array<String>, allowedMultiple: Boolean): Intent {
return Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = if (!types.isNullOrEmpty()) {
putExtra(Intent.EXTRA_MIME_TYPES, types)
types[0]
} else "*/*"
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowedMultiple)
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
}
This is in OnActivityResult
private fun handleGalleryActivityResult(data: Intent?, callbacks: FilePicker.Callbacks) {
if (data == null) return
val files = mutableListOf<Uri>()
when {
data.clipData != null -> {
val clipData = data.clipData ?: return
(0 until clipData.itemCount).forEach { files.add(clipData.getItemAt(it).uri) }
}
data.data != null -> {
files.add(data.data!!)
}
else -> return
}
files.forEach {
val flags = data.flags and Intent.FLAG_GRANT_READ_URI_PERMISSION
activity.contentResolver.takePersistableUriPermission(it, flags)
}
callbacks.onFilesPicked(files)
}
I am getting crash in line
activity.contentResolver.takePersistableUriPermission(it, flags)
in onActivityResult.
I read many solutions regarding this crash like adding persistable (FLAG_GRANT_PERSISTABLE_URI_PERMISSION) flag or adding takePersistableUriPermission but I have already have this but still I am getting this crash . I couldn't find any solution till now and my app users are facing this issue also on my phone I am not able to reproduce it myself.
Also on side note: I am using target version -> 11
Replace:
val flags = data.flags and Intent.FLAG_GRANT_READ_URI_PERMISSION
with:
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
The only values that you pass to takePersistableUriPermission() are FLAG_GRANT_READ_URI_PERMISSION and FLAG_GRANT_WRITE_URI_PERMISSION, and you have no idea what data.flags has in it.

Starting an activity intent from a broadcast receiver shows the wrong activity

I am making a library that can be used to incorporate breaking and entering detection into any application
There is an arduino set to the alarm of the house which sends an SMS to a specific phone upon trigger
Within my sdk I register an sms receiver which upon receiving an sms with a specific text, should show a full screen activity (on top of the lockscreen too) that will alert the user
I created an application to test this behaviour
the application's package is : com.example.demo
the library's package is : com.example.sdk
the sms receiver looks like this:
class SMSReceiver : BroadcastReceiver() {
companion object {
const val TAG = "SMSReceiver"
}
private val logger by lazy { Injections.logger }
override fun onReceive(context: Context?, intent: Intent?) {
logger.log(TAG) { "Got sms" }
val ctx = context ?: return
val bundle = intent?.extras ?: return
val format = bundle.getString("format") ?: return
val pdus = (bundle["pdus"] as? Array<*>) ?: return
for (idx in pdus.indices) {
val pdu = pdus[idx] as? ByteArray ?: continue
val msg = SmsMessage.createFromPdu(pdu, format)
if (msg.messageBody.startsWith("theft event", true)) {
logger.log(TAG) { "Got theft event" }
abortBroadcast()
showTheftActivity(ctx, msg.messageBody)
break
}
}
}
private fun showTheftActivity(context: Context, messageBody: String) {
val intent = Intent(context, TheftActivity::class.java)
intent.addFlags(Intent.FLAG_FROM_BACKGROUND)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
// .addCategory(Intent.CATEGORY_LAUNCHER)
val location = messageBody.split(" ").getOrNull(2)
if (location != null) {
val coords = location.split(",")
if (coords.size == 2) {
val x = coords[0].toBigDecimalOrNull()
val y = coords[1].toBigDecimalOrNull()
if (x != null && y != null) {
intent.putExtra(TheftActivity.X, x.toString())
intent.putExtra(TheftActivity.Y, y.toString())
}
}
}
context.startActivity(intent)
}
}
the activity that should show on top of everything is this :
class TheftActivity : Activity() {
companion object {
const val X = "locationX"
const val Y = "locationY"
}
private val x: String? by lazy { intent.getStringExtra(X) }
private val y: String? by lazy { intent.getStringExtra(Y) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_theft)
val location = findViewById<Button>(R.id.locate)
if (x != null && y != null) {
location.setOnClickListener { Toast.makeText(applicationContext, "Going to $x , $y", Toast.LENGTH_SHORT).show() }
location.isEnabled = true
finish()
} else {
location.isEnabled = false
}
findViewById<Button>(R.id.cancel).setOnClickListener {
finish()
}
turnScreenOnAndKeyguardOff()
}
private fun turnScreenOnAndKeyguardOff() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setShowWhenLocked(true)
setTurnScreenOn(true)
} else {
window.addFlags(
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
or WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
)
}
with(getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
requestDismissKeyguard(this#TheftActivity, null)
}
}
}
override fun onDestroy() {
super.onDestroy()
turnScreenOffAndKeyguardOn()
}
private fun turnScreenOffAndKeyguardOn() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setShowWhenLocked(false)
setTurnScreenOn(false)
} else {
window.clearFlags(
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
or WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
)
}
}
}
and the sdk's android manifest contains this:
<application>
<activity
android:name=".ui.TheftActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:excludeFromRecents="true"
android:label="#string/title_activity_theft"
android:showOnLockScreen="true"
android:theme="#style/Theme.Sdk.Fullscreen" />
<receiver
android:name=".receivers.SMSReceiver"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BROADCAST_SMS">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
</application>
when testing this on an emulator I send the sms to trigger the theft event
if the activity for testing (the one in the com.example.demo package) is not closed then it is brought to the front , but if it is closed nothing happens (though I do see the log messages from the receiver)
how can I make my sms receiver open the TheftActivity instead of the main activity from the main package?
edit: if it helps, the theft activity seems to start and then get immediately destroyed
It looks like the system can't bring the activity to the foreground due to the restrictions implemented in Android Q
With Android Q, it is impossible to start an activity from the background automatically if your app does not include those exceptions listed in the link below.
https://developer.android.com/guide/components/activities/background-starts
For possible solutions :
https://stackoverflow.com/a/59421118/11982611

Write permissions not working - scoped storage Android SDK 30 (aka Android 11)

Anyone else finding scoped-storage near-impossible to get working? lol.
I've been trying to understand how to allow the user to give my app write permissions to a text file outside of the app's folder. (Let's say allow a user to edit the text of a file in their Documents folder). I have the MANAGE_EXTERNAL_STORAGE permission all set up and can confirm that the app has the permission. But still every time I try
val fileDescriptor = context.contentResolver.openFileDescriptor(uri, "rwt")?.fileDescriptor
I get the Illegal Argument: Media is read-only error.
My manifest requests these three permissions:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
I've also tried using legacy storage:
<application
android:allowBackup="true"
android:requestLegacyExternalStorage="true"
But still running into this read-only issue.
What am I missing?
extra clarification
How I'm getting the URI:
view?.selectFileButton?.setOnClickListener {
val intent =
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
}
startActivityForResult(Intent.createChooser(intent, "Select a file"), 111)
}
and then
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 111 && resultCode == AppCompatActivity.RESULT_OK && data != null) {
val selectedFileUri = data.data;
if (selectedFileUri != null) {
viewModel.saveFilename(selectedFileUri.toString())
val contentResolver = context!!.contentResolver
val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
contentResolver.takePersistableUriPermission(selectedFileUri, takeFlags)
view?.fileName?.text = viewModel.filename
//TODO("if we didn't get the permissions we needed, ask for permission or have the user select a different file")
}
}
}
You may try the code below. It works for me.
class MainActivity : AppCompatActivity() {
private lateinit var theTextOfFile: TextView
private lateinit var inputText: EditText
private lateinit var saveBtn: Button
private lateinit var readBtn: Button
private lateinit var deleteBtn: Button
private lateinit var someText: String
private val filename = "theFile.txt"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (!isPermissionGranted()) {
val permissions = arrayOf(WRITE_EXTERNAL_STORAGE)
for (i in permissions.indices) {
requestPermission(permissions[i], i)
}
}
theTextOfFile = findViewById(R.id.theTextOfFile)
inputText = findViewById(R.id.inputText)
saveBtn = findViewById(R.id.saveBtn)
readBtn = findViewById(R.id.readBtn)
deleteBtn = findViewById(R.id.deleteBtn)
saveBtn.setOnClickListener { savingFunction() }
deleteBtn.setOnClickListener { deleteFunction() }
readBtn.setOnClickListener {
theTextOfFile.text = readFile()
}
}
private fun readFile() : String{
val rootPath = "/storage/emulated/0/Download/"
val myFile = File(rootPath, filename)
return if (myFile.exists()) {
FileInputStream(myFile).bufferedReader().use { it.readText() }
}
else "no file"
}
private fun deleteFunction(){
val rootPath = "/storage/emulated/0/Download/"
val myFile = File(rootPath, filename)
if (myFile.exists()) {
myFile.delete()
}
}
private fun savingFunction(){
deleteFunction()
someText = inputText.text.toString()
val resolver = applicationContext.contentResolver
val values = ContentValues()
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
values.put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
values.put(MediaStore.MediaColumns.MIME_TYPE, "text/plain")
values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
val uri = resolver.insert(MediaStore.Files.getContentUri("external"), values)
uri?.let { it ->
resolver.openOutputStream(it).use {
// Write file
it?.write(someText.toByteArray(Charset.defaultCharset()))
it?.close()
}
}
} else {
val rootPath = "/storage/emulated/0/Download/"
val myFile = File(rootPath, filename)
val outputStream: FileOutputStream
try {
if (myFile.createNewFile()) {
outputStream = FileOutputStream(myFile, true)
outputStream.write(someText.toByteArray())
outputStream.flush()
outputStream.close()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
private fun isPermissionGranted(): Boolean {
val permissionCheck = ActivityCompat.checkSelfPermission(this, WRITE_EXTERNAL_STORAGE)
return permissionCheck == PackageManager.PERMISSION_GRANTED
}
private fun requestPermission(permission: String, requestCode: Int) {
ActivityCompat.requestPermissions(this, arrayOf(permission), requestCode)
}
}
In terms of your code:
None of your listed permissions have anything to do with ACTION_OPEN_DOCUMENT
Neither of the flags on your Intent belong there
Your real problem, though, is that you appear to be choosing media, such as from the Audio category. ACTION_OPEN_DOCUMENT guarantees that we can read from the content identified by the Uri, but it does not guarantee a writeable location. Unfortunately, MediaProvider blocks all write access, throwing the exception whose message you cited.
Quoting myself from the issue that I filed last year:
The problem is that we have no way of specifying on the ACTION_OPEN_DOCUMENT Intent that we intend to write and therefore want to limit the user to writable locations. Given that Android Q/R are putting extra emphasis on us migrating to the Storage Access Framework, this sort of feature is needed. Otherwise, all we can do is detect that we do not have write access (e.g., DocumentFile and canWrite()), then tell the user "sorry, I can't write there", which leads to a bad user experience.
I wrote a bit more about this problem in this blog post.
So, use DocumentFile and canWrite() to see if you are allowed to write to the location identified by the Uri, and ask the user to choose a different document.
On Android 11 and testing with API 30 emulators i found public folders like
Download, Documents, DCIM, Alarms, Pictures and such
writable for my apps using classic file system paths.
Restricted to app's own files.
Further i found that files created by one app in this way were writeble by a different app using SAF.

Kotlin how to properly use Android Intent to save file

I am currently trying to save a text file inside a fragment, but I can't get it to work:
Here is the method called when the user clicks the save button
private fun saveText(){
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
putExtra(Intent.EXTRA_TITLE, "text.txt")
}
startActivityForResult(intent, 1)
}
Here is the onActivityResult method:
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
if (requestCode == 1 && resultCode == Activity.RESULT_OK) {
try {
val path = resultData?.data?.path
Log.wtf("Path", filePath)
val writer: Writer = BufferedWriter(FileWriter(path))
writer.write("Example Text")
writer.close()
} catch (e: Exception){
e.printStackTrace()
}
}
}
I also have the permissions set in the manifest and the file itself is created, but nothing is written. Perhaps I'm writing to the file wrong?
The error thrown is FileNotFoundException, because its trying to use a file from /document when I'm selecting one from /downloads
Suggested solution which unfortunately doesn't work:
resultData?.data?.let {
requireActivity().contentResolver.openOutputStream(it).use { stream ->
stream!!.bufferedWriter().write("Example Text")
}
}
A Uri is not a file.
Replace:
val path = resultData?.data?.path
Log.wtf("Path", filePath)
val writer: Writer = BufferedWriter(FileWriter(path))
writer.write("Example Text")
writer.close()
with:
resultData?.data?.let { contentResolver.openOutputStream(it).use { stream ->
stream.writer().write("Example Text")
}
}

Categories

Resources