How to share the THIS applications base.apk using Intent? [duplicate] - android

This question already has answers here:
exposed beyond app through ClipData.Item.getUri
(4 answers)
Closed 8 months ago.
I want to share my application using a share button inside it. Once the button is clicked it should get the base.apk from the package manager and then share it using Intents.
Here is what I have so far:
All UI is ready and working
I have the following code to get the app and share it
try {
val pm = packageManager
val ai = pm.getApplicationInfo(packageName, 0)
val srcFile = File(ai.publicSourceDir)
val share = Intent()
share.action = Intent.ACTION_SEND
share.type = "application/vnd.android.package-archive"
share.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(srcFile))
startActivity(Intent.createChooser(share, "Sharing"))
} catch (e: Exception) {
UtilityMethods(this).toast("Failed To Share The App", "e")
e.printStackTrace()
}
But I get an error with this procedure.
android.os.FileUriExposedException: file:///data/app/~~BC-clKZDViP_O7n44ooPbQ%3D%3D/MyAppPublicSourceDirectory/base.apk exposed beyond app through ClipData.Item.getUri()
Is there any help I can get regarding this? I tried a lot of solutions, but they don't work for me.
EDIT:: Updated Code, Copy the base.apk to Downloads Folder and Rename it. Then try to share it (which is where the error invokes from).
try {
// get the base apk of the app
val pm = packageManager
val ai = pm.getApplicationInfo(packageName, 0)
val srcFile = File(ai.publicSourceDir)
// save the file in Downloads folder
val dstFile = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
"LogsCalculator.apk"
)
dstFile.createNewFile()
val input = FileInputStream(srcFile)
val output = FileOutputStream(dstFile)
val buffer = ByteArray(1024)
var length: Int = input.read(buffer)
while (length > 0) {
output.write(buffer, 0, length)
length = input.read(buffer)
}
output.flush()
output.close()
input.close()
// share the apk file now
val intent = Intent(Intent.ACTION_SEND)
intent.type = "application/vnd.android.package-archive"
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(dstFile))
startActivity(Intent.createChooser(intent, getString(R.string.sharing)))
} catch (e: Exception) {
UtilityMethods(this).toast("Failed To Share The App", "e")
e.printStackTrace()
}
It Still Does Not Work.

On later API versions, not even backup tools can obtain the APK anymore, therefore the approach is literally pointless. Instead use Firebase Dynamic Links, in order to permit user to user sharing of your application. This way they can install from Google Play Store, instead up installing some APK of unknown origin, which may not update well.

I got the solution.
try {
// get the base.apk
val baseApkLocation =
applicationContext.packageManager.getApplicationInfo(
applicationContext.packageName,
PackageManager.GET_META_DATA
).sourceDir
// get the file
val baseApk = File(baseApkLocation)
// the path
val path = Environment.getExternalStorageDirectory().toString() + "/Download/"
// make the directory
val dir = File(path)
// if the directory doesn't exist, make it
if (!dir.exists()) {
dir.mkdirs()
}
// Copy the .apk file to downloads directory
val destination = File(
path + "MyAppName.apk"
)
if (destination.exists()) {
destination.delete()
}
destination.createNewFile()
val input = FileInputStream(baseApk)
val output = FileOutputStream(destination)
val buffer = ByteArray(1024)
var length: Int = input.read(buffer)
while (length > 0) {
output.write(buffer, 0, length)
length = input.read(buffer)
}
output.flush()
output.close()
input.close()
// get content uri for the file
val uri = FileProvider.getUriForFile(
this,
BuildConfig.APPLICATION_ID + ".provider",
destination
)
// share the file
val intent = Intent(Intent.ACTION_SEND)
intent.type = "application/vnd.android.package-archive"
intent.putExtra(Intent.EXTRA_STREAM, uri)
startActivity(Intent.createChooser(intent, getString(R.string.share_app)))
} catch (e: Exception) {
Lib(this).toast("Failed To Share The App", "e")
e.printStackTrace()
}
Here is how it works
Get the base.apk file of the app from its source dir.
Copy the file to the new location and give it a meaningful name.
Get the content URI of the new file. This is what was missing.
// get content uri for the file
val uri = FileProvider.getUriForFile(
this,
BuildConfig.APPLICATION_ID + ".provider",
destination
)
Share the file.
Anyways thanks for all the help.

Related

How to open a file in another app via an Intent using an URI?

In my app, I have several file Uri (NOT URI) store. These are of the form:
content://com.android.providers.downloads.documents/document/427
My intention is, once I click a button, open one of these Uri in a file reader (may it be MS Word, Excel, PDF Reader... depending on the extension and device).
I am currently trying this snippet:
val file = File(Uri.parse(uri).path!!)
val myMime: MimeTypeMap = MimeTypeMap.getSingleton()
val newIntent = Intent(Intent.ACTION_VIEW)
val mimeType: String = myMime.getMimeTypeFromExtension(file.extension).toString()
newIntent.setDataAndType(
FileProvider.getUriForFile(applicationContext, "${applicationContext.packageName}.provider", file), mimeType)
newIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
newIntent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
try {
startActivity(newIntent)
} catch (e: ActivityNotFoundException) {
}
But every time I try to open it, I get:
java.lang.IllegalArgumentException: Failed to find configured root that contains /document/427
Could you please tell me what am I missing? No answers I've found here targeted my problem. Thanks in advance!
UPDATE
Thanks to #CommonsWare help, I managed to avoid an Exception, my code now looks like this:
val newIntent = Intent(Intent.ACTION_VIEW)
newIntent.addCategory(Intent.CATEGORY_OPENABLE)
newIntent.data = Uri.parse(uri)
newIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
newIntent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
try {
startActivity(newIntent)
} catch (e: ActivityNotFoundException) {
Toast.makeText(this, e.toString(), Toast.LENGTH_LONG).show()
}
I'm getting the Uri from here:
val fileResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
viewModel.uploadFile(result, false)
}
fileFab.setOnClickListener {
val intent = Intent()
intent.type = "application/*"
intent.action = Intent.ACTION_OPEN_DOCUMENT
fileResultLauncher.launch(intent)
}
Unfortunately, I'm getting a NoActivityFoundException
Get rid of:
val file = File(Uri.parse(uri).path!!)
...and replace your setDataAndType() call with:
newIntent.setDataAndType(Uri.parse(uri), mimeType)

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.

How can we save a file of any type from an InputStream using Kotlin for Android?

Context:
My project is in the space of WiFi P2P/ WiFi Direct. I have found and followed some useful tutorials and questions and have a working app for sending Images between phones.
A socket is opened and an OutputStream sent to the other device. Device 2 receives the stream and has to assume the mime Type in order to compress and save to a file.*
EDIT: *Only has to compress for images, but needs to know the File type to save it to storage.
I would now like to open up this feature to transfer a file of any (common) type and have the other device happily receive it and stash it in the appropriate location (Or just all to Downloads might make it simpler).
My question:
What can I use to determine the MIME type for all (image, text, calendar events, contacts, videos) files coming in as an InputStream.
Current code attempts:
I have tried using BitmapFactory.decodeStream() to receive options and mimeType. This only works for Images.
guessContentTypeFromStream() hasn't worked at all and I think it might only be suitable for streaming from internet source?
Any clarifications/tips on how Storage access framework should be used here, or techniques to determine mimeType are appreciated!
Below is code used on the Receive side:
inner class RxClass: Thread() {
lateinit var socket: Socket
lateinit var serverSocket: ServerSocket
override fun run() {
try {
serverSocket = ServerSocket(8888)
socket = serverSocket.accept()
//If this line is reached, then a connection has been established between server and client
Log.d("RECEIVE", "Connection established")
lateinit var bitmap: Bitmap
lateinit var inputStream: InputStream
lateinit var outputStream: OutputStream
var mimeType: String? = null
lateinit var path: String
try {
inputStream = socket.getInputStream()
Log.d("RECEIVE", "got InputStream")
runOnUiThread { Toast.makeText(applicationContext, "Receiving file...", Toast.LENGTH_SHORT).show() }
//mimeType = guessContentTypeFromStream(inputStream)
val options = BitmapFactory.Options()
bitmap = BitmapFactory.decodeStream(inputStream, null, options)!! //TODO: this needs to be for all file types
mimeType = options.outMimeType
Log.d("RECEIVE", "Decoded input, Type: $mimeType")
} catch (e: IOException) {
e.printStackTrace()
}
when(mimeType){ //I was hoping that my attempt would let me know what MIME type was and then here I can act on it differently if neccessary
"image/jpeg" -> path = Environment.DIRECTORY_PICTURES
"image/jpg" -> path = Environment.DIRECTORY_PICTURES
"image/bmp" -> path = Environment.DIRECTORY_PICTURES
"image/gif" -> path = Environment.DIRECTORY_PICTURES
"image/png" -> path = Environment.DIRECTORY_PICTURES
"audio/mpeg" -> path = Environment.DIRECTORY_MUSIC
"audio/x-wav" -> path = Environment.DIRECTORY_MUSIC
"video/wav" -> path = Environment.DIRECTORY_MOVIES
"video/mp4" -> path = Environment.DIRECTORY_MOVIES
"text/plain" -> path = Environment.DIRECTORY_DOCUMENTS
"text/html" -> path = Environment.DIRECTORY_DOCUMENTS
"text/x-vcard" -> path = Environment.DIRECTORY_DOCUMENTS
"text/x-vcalendar" -> path = Environment.DIRECTORY_DOCUMENTS
null -> Log.d("RECEIVE", "MIMETYPE is NULL")
}
Log.d("RECEIVE", "Save file in location: $path")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val resolver: ContentResolver = contentResolver
val contentValues = ContentValues()
//contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, name)
//contentValues.put(MediaStore.Images.Media.MIME_TYPE, mimeType)
//contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, path)
val rxUri: Uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)!!
val inputStream = socket.getInputStream()
outputStream = resolver.openOutputStream(rxUri)!!
Log.d("RECEIVE", "Sending File: $rxUri")
inputStream.copyTo(outputStream)
if(bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)) {
runOnUiThread { Toast.makeText(applicationContext, "File Saved", Toast.LENGTH_SHORT).show() }
}
Log.d("RECEIVE", "close streams")
outputStream.flush()
outputStream.close()
inputStream.close()
runOnUiThread {
ReceivedImage.setImageURI(rxUri)
}
} else { //Version less than Android Q is not implemented
}
} catch (e: IOException){
e.printStackTrace()
}
}
}
Useful links
https://developer.android.com/training/data-storage
https://developer.android.com/training/data-storage/shared/media#add-item
https://developer.android.com/reference/kotlin/android/graphics/BitmapFactory
Thanks in advance

Kotlin - How can open a csv file via Intent using default apps?

My android app flow is as follows:
Creates a csv file.
Store it in the default app folder.
Generates a notification of succesful creation
Open the csv in the default app.
I can not be able to open the csv via Intent or other methods so far.
With the following code I'm able to open the explorer but not in the folder path.
var path = this.applicationContext.getExternalFilesDir(null)?.absolutePath.toString()
println("Hey my path is $path")
val csvWriter = CSVWriter(
fileWriter,
CSVWriter.DEFAULT_SEPARATOR,
CSVWriter.NO_QUOTE_CHARACTER,
CSVWriter.DEFAULT_ESCAPE_CHARACTER,
CSVWriter.DEFAULT_LINE_END
)
csvWriter.writeAll(data.toMutableList())
csvWriter.close()
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/*"
}
startActivityForResult(intent, 2)
Also, I tried to develop a function but without effective response:
fun open_file(filename: String?) {
val path = File(filesDir, "dl")
val file = File(path, filename)
// Get URI and MIME type of file
val uri = FileProvider.getUriForFile(this, "$PACKAGE_NAME.fileprovider", file)
val mime = contentResolver.getType(uri)
// Open file with user selected app
val intent = Intent()
intent.action = Intent.ACTION_VIEW
intent.setDataAndType(uri, mime)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivity(intent)
}
I saw some possible solutions developed in java but there are deprecated and / or does not work converted in Kotlin.
Edit:
Given the suggestion of #CommonsWare, I'm trying to save the file in the path given but the user. However, I get the error : No Activity found to handle Intent
val FILE_EXPORT_REQUEST_CODE = 12
val exportIntent = Intent(Intent.ACTION_CREATE_DOCUMENT)
exportIntent.addCategory(Intent.CATEGORY_OPENABLE)
exportIntent.type = "text/csv"
val uri = Uri.parse("$path/file.csv")
exportIntent.data = uri
val filename = "file.csv"
exportIntent.putExtra(Intent.EXTRA_TITLE, filename)
startActivityForResult(exportIntent, FILE_EXPORT_REQUEST_CODE)
Thanks in advance.

Android: Not able to open csv file which is downloaded from DownloadManager

I'm trying to open the CSV file downloaded using DownloadManager using the apps available in the phone. But I am not able to open it. The third party apps returns giving error Toast messages or Alert Dialog saying file location not found. But when, I open file manually, the app is getting opened normally.
I have written below code for accessing the downloaded file and opening using Intent Chooser.
fun openDownloadedFile(referenceId : Long){
if (downloadReference === referenceId)
{
val query = DownloadManager.Query()
query.setFilterById(downloadReference)
var downloadManager = activity?.getSystemService(DOWNLOAD_SERVICE) as DownloadManager?
val c = downloadManager?.query(query)!!
if (c.moveToFirst())
{
val columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS)
if (DownloadManager.STATUS_SUCCESSFUL === c.getInt(columnIndex))
{
var localUri:String = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
if (localUri.substring(0, 7).equals("file://")) {
localUri = localUri.substring(7);
}
var file = File(localUri);
val fileExtension = MimeTypeMap.getFileExtensionFromUrl(localUri)
var mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension)
if (mimeType != null)
{
mimeType = "text/csv" // even after removing manual mime type it is not working.
var intent = Intent(Intent.ACTION_VIEW);
//var file = File(localUri);
var absoluteFilePath = file.getAbsolutePath();
var uri = Uri.parse( "content:/"+absoluteFilePath);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.setDataAndTypeAndNormalize(uri, mimeType);
try
{
// Add chooser to open file.
var intentChooser = Intent.createChooser(intent, "Choose Application");
startActivityForResult(intentChooser,1000);
}
catch (e:ActivityNotFoundException) {
}
}
} else {
Log.d(“TAG”, "Download Failed.. status : "+c.getInt(columnIndex))
}
}
}
}
When I chose, Microsoft Excel, it shows Cant open file error.
Any help is appreciated.

Categories

Resources