I have the following code below for exporting a room database and then attaching it to an email. Currently the user first has to choose where they want the data saved before it can be attached.
Is there a way that I can do this without first having to ask the user where to save the database?
Here is my code:
fun exportDatabase() {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
intent.type = "*/*" // this line is a must when using ACTION_CREATE_DOCUMENT
startActivityForResult(
intent,
DATABASE_EXPORT_CODE
)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
DATABASE_EXPORT_CODE -> {
val userChosenUri = data?.data
val inStream = getDatabasePath("app_database").inputStream()
val outStream = userChosenUri?.let { contentResolver.openOutputStream(it) }
inStream.use { input ->
outStream.use { output ->
output?.let { input.copyTo(it) }
Toast.makeText(this, "Data exported successfully", Toast.LENGTH_LONG).show()
val emailIntent = Intent(Intent.ACTION_SEND)
//Set type to email
emailIntent.type = "vnd.android.cursor.dir/email"
var toEmail: String = "whatever#gmail.com"
emailIntent.putExtra(Intent.EXTRA_EMAIL, toEmail)
emailIntent.putExtra(Intent.EXTRA_STREAM, userChosenUri)
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Data for Training Log")
startActivity(Intent.createChooser(emailIntent, "Send Email"))
}
}
}
else ->
Log.d("D001", "onActivityResult: unknown request code")
}
}
You need to use FileProvider. But FileProvider doesn't support transferring database files directly (Check here).
This can handled with:
Solution 1:
Create a custom FileProvider class that supports copying database files:
class DBFileProvider : FileProvider {
fun getDatabaseURI(c: Context, dbName: String?): Uri? {
val file: File = c.getDatabasePath(dbName)
return getFileUri(c, file)
}
private fun getFileUri(context: Context, file: File): Uri? {
return getUriForFile(context, "com.android.example.provider", file)
}
}
And request the FileProvider in manifest:
<application>
....
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.android.example.provider"
android:exported="false"
android:enabled="true"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths" />
</provider>
</application>
And create provider_paths under res\xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path
name="databases"
path="../" />
</paths>
Then to send this database file through the email:
public static void backupDatabase(AppCompatActivity activity) {
Uri uri = new DBFileProvider().getDatabaseURI(activity, "app_database.db");
sendEmail(activity, uri);
}
private fun sendEmail(activity: AppCompatActivity, attachment: Uri) {
val emailIntent = Intent(Intent.ACTION_SEND)
//Set type to email
emailIntent.type = "vnd.android.cursor.dir/email"
val toEmail = "whatever#gmail.com"
emailIntent.putExtra(Intent.EXTRA_EMAIL, toEmail)
emailIntent.putExtra(Intent.EXTRA_STREAM, attachment)
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Data for Training Log")
activity.startActivity(Intent.createChooser(emailIntent, "Send Email"))
}
Solution 2:
Copy the database file to a temp file to a directory supported by FileProvider like filesDir:
Get the database file using getDatabasePath
Copy the database file to a storage directory that is supported by FileProvider
Create the Uri of the new copied file using the FileProvider
fun backupDatabase(activity: AppCompatActivity) {
// Get the database file
val dbFile = activity.getDatabasePath("app_database.db")
try {
// Copy database file to a temp file in (filesDir)
val parent = File(activity.filesDir, "databases_temp")
val file = File(parent, "myDatabase")
dbFile.copyTo(file)
// Get Uri of the copied database file from filesDir to be used in email intent
val uri = getUri(activity.applicationContext, file)
// Send an email
sendEmail(activity, uri)
} catch (e: IOException) {
e.printStackTrace()
}
}
private fun getUri(context: Context, file: File): Uri {
var uri = Uri.fromFile(file)
// Using FileProvider for API >= 24
if (Build.VERSION.SDK_INT >= 24) {
uri = FileProvider.getUriForFile(
context,
"com.android.example.provider", file
)
}
return uri
}
Use the same manifest of solution 1. And adjust provider_paths under res\xml with the created temp dir:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path
name="databases_temp"
path="/" />
</paths>
N.B: In both solutions, adjust the package name to yours.
Related
I am using this code and I am missing something, because almost everything is working, but I get a null in the data when the callback responds:
private inner class JavascriptInterface {
#android.webkit.JavascriptInterface
fun image_capture() {
val photoFileName = "photo.jpg"
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
var photoFile = getPhotoFileUri(photoFileName)
if (photoFile != null) {
fileProvider = FileProvider.getUriForFile(applicationContext, "com.codepath.fileprovider", photoFile!!)
intent.putExtra(EXTRA_OUTPUT, fileProvider)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
if (intent.resolveActivity(packageManager) != null) {
getContent.launch(intent)
}
}
}
}
val getContent = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK) {
val intent:Intent? = result.data // <- PROBLEM: data is ALWAYS null
}
}
My manifest snippet related to this looks like this:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
and my fileprovider.xml looks like this:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-files-path name="images" path="Pictures" />
</paths>
Any help is appreciated. Thanks!
That is supposed to be null, as ACTION_IMAGE_CAPTURE is not documented to return a Uri. You are using EXTRA_OUTPUT. The image should be stored in the location that you specified using EXTRA_OUTPUT.
Note, though, that you should be adding both FLAG_GRANT_READ_URI_PERMISSION and FLAG_GRANT_WRITE_URI_PERMISSION to the Intent, as the camera app needs to be able to write the image to the desired location.
So, I ended up checking out the TakePicture contract #ian (thanks for that tip!) and after a lot of cobbling together various resources I found, I finally got it to work. This is the pertinent kotlin code for the webview Activity:
class WebViewShell : AppCompatActivity() {
val APP_TAG = "MyApp"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_web_view)
// Storing data into SharedPreferences
val sharedPreferences = getSharedPreferences("MySharedPrefs", MODE_PRIVATE)
val storedurl: String = sharedPreferences.getString("url", "").toString()
val myWebView: WebView = findViewById(R.id.webview_webview)
myWebView.clearCache(true)
myWebView.settings.setJavaScriptCanOpenWindowsAutomatically(true)
myWebView.settings.setJavaScriptEnabled(true)
myWebView.settings.setAppCacheEnabled(true)
myWebView.settings.setAppCacheMaxSize(10 * 1024 * 1024)
myWebView.settings.setAppCachePath("")
myWebView.settings.setDomStorageEnabled(true)
myWebView.settings.setRenderPriority(android.webkit.WebSettings.RenderPriority.HIGH)
WebView.setWebContentsDebuggingEnabled(true)
myWebView.addJavascriptInterface(JavascriptInterface(),"Android")
myWebView.loadUrl(storedurl)
}
private inner class JavascriptInterface {
#android.webkit.JavascriptInterface
fun image_capture() { // opens Camera
takeImage()
}
}
private fun takeImage() {
try {
val uri = getTmpFileUri()
lifecycleScope.launchWhenStarted {
takeImageResult.launch(uri)
}
}
catch (e: Exception) {
android.widget.Toast.makeText(applicationContext, e.message, android.widget.Toast.LENGTH_LONG).show()
}
}
private val takeImageResult = registerForActivityResult(TakePictureWithUriReturnContract()) { (isSuccess, imageUri) ->
val myWebView: android.webkit.WebView = findViewById(R.id.webview_webview)
if (isSuccess) {
val imageStream: InputStream? = contentResolver.openInputStream(imageUri)
val selectedImage = BitmapFactory.decodeStream(imageStream)
val scaledImage = scaleDown(selectedImage, 800F, true)
val baos = ByteArrayOutputStream()
scaledImage?.compress(Bitmap.CompressFormat.JPEG, 100, baos)
val byteArray: ByteArray = baos.toByteArray()
val dataURL: String = Base64.encodeToString(byteArray, Base64.DEFAULT)
myWebView.loadUrl( "JavaScript:fnWebAppReceiveImage('" + dataURL + "')" )
}
else {
android.widget.Toast.makeText(applicationContext, "Image capture failed", android.widget.Toast.LENGTH_LONG).show()
}
}
private inner class TakePictureWithUriReturnContract : ActivityResultContract<Uri, Pair<Boolean, Uri>>() {
private lateinit var imageUri: Uri
#CallSuper
override fun createIntent(context: Context, input: Uri): Intent {
imageUri = input
return Intent(MediaStore.ACTION_IMAGE_CAPTURE).putExtra(MediaStore.EXTRA_OUTPUT, input)
}
override fun getSynchronousResult(
context: Context,
input: Uri
): SynchronousResult<Pair<Boolean, Uri>>? = null
#Suppress("AutoBoxing")
override fun parseResult(resultCode: Int, intent: Intent?): Pair<Boolean, Uri> {
return (resultCode == Activity.RESULT_OK) to imageUri
}
}
private fun getTmpFileUri(): Uri? {
val mediaStorageDir = File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), APP_TAG)
if (!mediaStorageDir.exists() && !mediaStorageDir.mkdirs()) {
throw Exception("Failed to create directory to store media temp file")
}
return FileProvider.getUriForFile(applicationContext, getApplicationContext().getPackageName() + ".provider", File(mediaStorageDir.path + File.separator + "photo.jpg"))
}
fun scaleDown(realImage: Bitmap, maxImageSize: Float, filter: Boolean): Bitmap? {
val ratio = Math.min(maxImageSize / realImage.width, maxImageSize / realImage.height)
val width = Math.round(ratio * realImage.width)
val height = Math.round(ratio * realImage.height)
return Bitmap.createScaledBitmap(realImage, width, height, filter)
}
}
To round things out, here is the pertinent JavaScript code - which the Activity is loading via the myWebView.loadUrl(storedurl) statement.
This is the JavaScript code which calls the Android code:
if (window.Android) {
Android.image_capture();
}
And when the picture has been taken, and sized by the Android code, it sends the Base64 back to JavaScript with:
myWebView.loadUrl("JavaScript:fnWebAppReceiveImage('" + dataURL + "')")
Note how weirdly you have to specify function arguments. There probably is a better way, but this code works. If there are any suggestions about how to specify a function argument easier than this, please let me know.
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.MyApp">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<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>
</manifest>
And the provider_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-files-path name="external_files" path="." />
</paths>
Hope this helps someone - it took me days of research to figure this one out!
I have an android homescreen widget created with the new Glance api which contains a lazy column. Each row in the column displays an image with ImageProvider(contentUri).
The image has been retrieved from a URL with Glide and saved to internal storage file with FileOutputStream(filename). see MainActivity below.
When attempting to retrieve and show the image with getUriForFile() my widget just shows a message "Loading..." and never displays the image. No crash occurs. It just never loads the image.
How can I pull the image from internal storage and display it in LazyColumnRow()?
Note that I am only showing one bitmap in this example as proof of concept, but intend to eventually show 10+ bitmaps. I am following the recommendation in below post, which suggests using URI for multiple images.
Crash in glance app widget image when trying to display bitmap
Note that when I pull the same URI in MainActivity() it works and displays the image in ImageView.
Composable defined in GlanceAppWidget Content()
#Composable
fun LazyColumnRow(
/*LazyColumnRow is called from a LazyColumn, with each filename passed in here*/
) {
val context = LocalContext.current
val filepath = File(context.getFilesDir(), "my_images")
val filename = File(filepath, "default_image.png") /*each LazyColumn item will have different filename in PROD*/
val contentUri: Uri = FileProvider.getUriForFile(context,"${context.packageName}.fileprovider", filename)
Row(modifier = GlanceModifier) {
Image(
modifier = GlanceModifier.size(28.dp),
provider = ImageProvider(contentUri), /*this is not working*/
contentDescription = "Image"
)
}
}
MainActivity class which downloads and stores Bitmap. It can also
succesfully retrieve URI and display in imageView.
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
#OptIn(ExperimentalAnimationApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = inflate(layoutInflater)
setContentView(binding.root)
//Download image from url
var bitmap: Bitmap? = null
CoroutineScope(Dispatchers.IO).launch {
bitmap = Glide.with(baseContext)
.asBitmap()
.load("https://picsum.photos/id/237/200")
.submit()
.get()
}
//store image in internal storage file
val filepath = File(baseContext.getFilesDir(), "my_images")
if (!filepath.exists()) {
filepath.mkdirs()
}
val filename = File(filepath, "default_image.png")
try {
FileOutputStream(filename).use { out ->
bitmap?.compress(Bitmap.CompressFormat.PNG, 100, out)
}
} catch (e: IOException) {
e.printStackTrace()
}
//retrieve image from internal storage file
val contentUri: Uri = getUriForFile(
baseContext,
"$packageName.fileprovider",
filename)
//display in imageView. This code works.
val imageView = findViewById<ImageView>(R.id.myImage)
imageView.setImageURI(contentUri)
}
}
Content Provider declared in Manifest for FileProvider URI
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
xml/file_paths
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="my_images" path="/" />
<files-path name="my_docs" path="docs/" />
</paths>
You need to explicitly grant read permission to the Home activities, for example with:
/**
* When sharing a content URI to an app widget, we need to grant read access to the Home activity.
*
* See https://stackoverflow.com/a/59108192/1474476
*/
fun Context.grantUriPermissionToHomeActivity(uri: Uri, flags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION) {
grantUriPermission(packageManager.homeActivityPackages(), uri, flags)
}
fun PackageManager.homeActivityPackages(): List<String> {
val intent = Intent(Intent.ACTION_MAIN)
intent.addCategory(Intent.CATEGORY_HOME)
return queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
.map { it.activityInfo.packageName }
}
fun Context.grantUriPermission(packages: List<String>, uri: Uri, flags: Int) {
for (name in packages) {
grantUriPermission(name, uri, flags)
}
}
FOR MODERATORS: I know there are already questions like this but all of those approaches endup giving bitmap through data.getExtra("data") which is actually just thumbnail. I want to get URI not bitmap and I need to get URI of ACTUAL IMAGE not its thumbnail with approach available in 2021. PLEASE CONSIDER THIS BEFORE TAGGING QUESTION AS DUPLICATE!
I am getting image from camera and its working fine on lower devices but its giving null in data when onActivityResult is called only in Android 10 and 11.
That's what I am doing
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
startActivityForResult(intent, ACTION_REQUEST_CAMERA)
Here is my onActivityResult
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_OK && requestCode == ACTION_REQUEST_CAMERA) {
data?.data?.let { uri ->
Toast.makeText(context, "Got URI", Toast.LENGTH_LONG).show()
}
}
}
This approach worked for me
In Manifest file
<application>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.android.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths">
</meta-data>
</provider>
...
</application
created a file /res/xml/file_paths.xml and specified path in that
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="my_images" path="Pictures" />
</paths>
In my activity
created a global variable var cameraPhotoFilePath: Uri? = null
this is how I started Camera acitivity for results
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
val photoFile: File? = try {
createImageFileInAppDir()
} catch (ex: IOException) {
// Error occurred while creating the File
null
}
photoFile?.also { file ->
val photoURI: Uri = FileProvider.getUriForFile(
this,
"com.example.android.provider",
file
)
cameraPhotoFilePath = photoURI
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
}
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
startActivityForResult(intent, ACTION_REQUEST_CAMERA)
here is a helper function that i used in above code
#Throws(IOException::class)
private fun createImageFileInAppDir(): File {
val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
val imagePath = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
return File(imagePath, "JPEG_${timeStamp}_" + ".jpg")
}
At the end in onActivityResult thats how I got image Uri
if (resultCode == RESULT_OK && requestCode == ACTION_REQUEST_CAMERA) {
cameraPhotoFilePath?.let { uri ->
// here uri is image Uri that was captured by camera
}
}
You should pass your own uri (path) to intent with action MediaStore.ACTION_IMAGE_CAPTURE
putExtra(MediaStore.EXTRA_OUTPUT, FileProvider.getUriForFile(
applicationContext,
"$packageName.your_file_provider",
File("path/to")
))
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
More info about FileProvider
Android developer have good documentation about how to get full sized photos . You can also get the uri of full size image .Please visit this link Get full size image
I read this so that upon clicking the button, the Camera intent is opened, and returned the saved picture file and path to the activity that called it, but I got my app crashed and restarted without opening the camera intent.
Manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:dist="http://schemas.android.com/apk/distribution"
package="com.oryx.geoop">
<uses-feature android:name="android.hardware.camera"
android:required="true" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA"/>
<application android:theme="#style/AppTheme">
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths">
</meta-data>
</provider>
<activity android:name=".AnotherActivity">
<intent-filter>
<action android:name="android.media.action.IMAGE_CAPTURE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>
The xml/file_paths
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
Activity
class AnotherActivity : AppCompatActivity() {
val REQUEST_TAKE_PHOTO = 1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_another)
// Grant permissions
Dexter.withActivity(this)
.withPermissions(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.CAMERA
)
.withListener(object : MultiplePermissionsListener {
override fun onPermissionRationaleShouldBeShown(
permissions: MutableList<PermissionRequest>?,
token: PermissionToken?
) {
/* ... */
}
})
save_local.setOnClickListener { captureFromCamera(this) }
}
#Throws(IOException::class)
private fun createImageFile(): File {
val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
val dir =
"""${getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)}/geoOp/"""
val storageDir = File(dir)
storageDir.mkdirs()
return File.createTempFile(
"JPEG_${timeStamp}_", /* prefix */
".jpg", /* suffix */
storageDir /* directory */
).apply {
// Save a file: path for use with ACTION_VIEW intents
val currentPhotoPath = absolutePath
}
}
private fun captureFromCamera(context: Context) {
Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
// Ensure that there's a camera activity to handle the intent
takePictureIntent.resolveActivity(context.packageManager)?.also {
// Create the File where the photo should go
val photoFile: File? = try {
createImageFile()
} catch (ex: IOException) {
// Error occurred while creating the File
null
}
// Continue only if the File was successfully created
photoFile?.also {
val photoURI: Uri = FileProvider.getUriForFile(
this,
"com.oryx.geoop.fileprovider",
it
)
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO)
}
}
}
}
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// Result code is RESULT_OK only if the user captures an Image
if (resultCode == Activity.RESULT_OK && resultCode == RESULT_OK) {
Toast.makeText(this, "pic saved", Toast.LENGTH_SHORT).show()
println("pic saved")
}
}
}
UPDATE
As getExternalStoragePublicDirectory is deprecated in Android Q, and to be replaced by below code, as shown here:
Kotlin
val resolver = context.contentResolver
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, "CuteKitten001")
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
put(MediaStore.MediaColumns.RELATIVE_PATH, "DCIM/PerracoLabs")
}
val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
resolver.openOutputStream(uri).use {
// TODO something with the stream
}
How can I use it in my code above, noting that this update return Uri, while original code returnfile` for image location:
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
// return Uri
} else {
// return file
}
I am trying to take a picture from camera in Android and I want to save it with a custom file name using UUID (eg: f12b5700-1d92-11e9-ab14-d663bd873d93.jpg).
In the following code, in onActivityResult, I do get the f12b5700-1d92-11e9-ab14-d663bd873d93.jpg in photoPath but when I check the actual image on device, it is saved as timestamp.jpg (eg: 1548082653944.jpg). My question is how can I make the image to be saved with custom name?
private var photoURI: Uri? = null
private fun takePhoto()
{
val values = ContentValues()
values.put(MediaStore.Images.Media.TITLE, UUID.randomUUID().toString() + ".jpg")
photoURI = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (intent.resolveActivity(packageManager) != null)
{
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
startActivityForResult(intent, PHOTO_REQUEST_CODE);
}
}
in onActivityResult handler, the code looks like this:
if (requestCode == PHOTO_REQUEST_CODE)
{
if (resultCode == Activity.RESULT_OK)
{
val proj = arrayOf(MediaStore.Images.Media.TITLE)
val cursor = contentResolver.query(photoURI!!, proj, null, null, null)
val index = cursor!!.getColumnIndex(MediaStore.Images.Media.TITLE)
cursor.moveToFirst()
photoPath = cursor.getString(index)
cursor.close()
Toast.makeText(this, photoPath, Toast.LENGTH_LONG).show()
}
else
{
capturedImage.setImageURI(null)
Toast.makeText(this, "Photo was not taken", Toast.LENGTH_SHORT).show()
}
}
With some struggle, I was able to solve the problem with the help of CommonsWare link:
https://github.com/commonsguy/cw-omnibus/tree/v8.13/Camera/FileProvider
Additionally I took help from these links:
https://guides.codepath.com/android/Accessing-the-Camera-and-Stored-Media
https://guides.codepath.com/android/Sharing-Content-with-Intents#sharing-files-with-api-24-or-higher
https://developer.android.com/training/camera/photobasics
https://developer.android.com/reference/android/os/Environment.html#getExternalStorageState(java.io.File)
The trick is to use FileProvider.
I had to add <provider> in AndroidManifest.xml
Working code looks like this:
if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED)
{
Toast.makeText(this, "Storage is not available", Toast.LENGTH_LONG).show()
return
}
var imageName = UUID.randomUUID().toString() + ".jpg"
var output = File(Environment.getExternalStoragePublicDirectory(appName), imageName)
if (!output.parentFile.exists() && !output.parentFile.mkdir())
{
Toast.makeText(this, "Unable to create storage folder", Toast.LENGTH_LONG).show()
return
}
photoURI = FileProvider.getUriForFile(this, authority, output)
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (intent.resolveActivity(packageManager) != null)
{
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
startActivityForResult(intent, PHOTO_REQUEST_CODE);
}
Provider:
<provider
android:authorities="com.domain.appname.fileprovider"
android:name="android.support.v4.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths"/>
</provider>
file_paths.xml:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="appname" path="appname" />
</paths>
Change com.domain.appname and appname accordingly.