-
-
Save amay077/bb30ba8681ef8916e3efb6edc86701c8 to your computer and use it in GitHub Desktop.
Helper for sending ACTION_IMAGE_CAPTURE intent and retrieve its results. Handles all low level operations
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<manifest xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:tools="http://schemas.android.com/tools" | |
package="your.awesome.app"> | |
<application> | |
<!-- need provider tag in application tag --> | |
<provider | |
android:name="android.support.v4.content.FileProvider" | |
android:authorities="${applicationId}.provider" | |
android:exported="false" | |
android:grantUriPermissions="true"> | |
<!-- ressource file to create --> | |
<meta-data | |
android:name="android.support.FILE_PROVIDER_PATHS" | |
android:resource="@xml/file_paths"> | |
</meta-data> | |
</provider> | |
</application> | |
</manifest> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<!-- locate res/xml/file_paths.xml --> | |
<paths xmlns:android="http://schemas.android.com/apk/res/android"> | |
<external-path name="external_files" path="." /> | |
</paths> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package your.awesome.app | |
//MIT License | |
//Copyright (c) 2015 Karol Wrótniak, Droids On Roids | |
import android.app.Activity | |
import android.content.ActivityNotFoundException | |
import android.content.ContentResolver | |
import android.content.ContentValues | |
import android.content.Context | |
import android.content.Intent | |
import android.net.Uri | |
import android.os.Environment | |
import android.provider.MediaStore | |
import android.provider.MediaStore.Images.ImageColumns | |
import android.provider.MediaStore.MediaColumns | |
import android.support.v4.content.FileProvider | |
import net.nepula.nippan_android.BuildConfig | |
import java.io.File | |
import java.util.regex.Pattern | |
/** | |
* Helper for sending ACTION_IMAGE_CAPTURE intent and retrieve its results. Handles all low level operations | |
* <br></br> | |
* Usage:<br></br> | |
* | |
* 1. Launch camera app by calling [.launchCameraApp], save resulting Uri, handle (or ignore) exceptions. | |
* 1. In your onActivityResult(int requestCode, int resultCode, Intent data) call [) ][.retrievePhotoResult]. You may want to inform user if photo cannot be read despite the result is RESULT_OK. | |
* | |
* @author koral-- | |
*/ | |
object ImageCaptureHelper { | |
private val PROJECTION = arrayOf(MediaColumns.DATA) | |
private val CONTENT_VALUES = ContentValues(1) | |
private val PHOTO_SHARED_PREFS_NAME = "photo_shared" | |
private val PHOTO_URI = "photo_uri" | |
/** | |
* Description text inserted into @link{ImageColumns.DESCRIPTION} column | |
*/ | |
val DESCRIPTION = "Photo taken with example application" | |
init { | |
CONTENT_VALUES.put(ImageColumns.DESCRIPTION, DESCRIPTION) | |
} | |
/** | |
* Tries to obtain File containing taken photo. Perform cleanups if photo was not taken or it is empty. | |
* @param caller caller Activity | |
* @param photoKey key in Shared Preferences for taken image, can be null | |
* @return File containing photo or null if no or empty photo was saved by camera app. | |
*/ | |
fun retrievePhotoResult(caller: Activity, photoKey: String?): File? { | |
var photoKey = photoKey | |
try { | |
if (photoKey == null) { | |
photoKey = PHOTO_URI | |
} | |
val prefs = caller.getSharedPreferences(PHOTO_SHARED_PREFS_NAME, Context.MODE_PRIVATE) | |
val takenPhotoUriString = prefs.getString(photoKey, null) | |
prefs.edit().remove(photoKey).commit() | |
if (takenPhotoUriString == null) { | |
return null | |
} | |
val takenPhotoUri = Uri.parse(takenPhotoUriString) | |
val cr = caller.contentResolver | |
val out = File(getPhotoFilePath(takenPhotoUri, cr)!!) | |
if (!out.isFile || out.length() == 0L) { | |
cr.delete(takenPhotoUri, null, null) | |
} else { | |
return out | |
} | |
} catch (ex: Exception) { | |
// no-op | |
} | |
return null | |
} | |
/** | |
* Tries to create photo placeholder and launch camera app | |
* @param requestCode your unique code that will be returned in onActivityResult | |
* @param caller caller Activity | |
* @param photoKey key in Shared Preferences for taken image, can be null | |
* @throws ActivityNotFoundException if no camera app was found | |
* @throws Exception if there is a problem with create photo eg. SDcard is not mounted. It may be eg. [IllegalStateException] or [UnsupportedOperationException] depending on [ContentResolver]. | |
*/ | |
@Throws(ActivityNotFoundException::class, Exception::class) | |
fun launchCameraApp(requestCode: Int, caller: Activity, photoKey: String?) { | |
var photoKey = photoKey | |
if (photoKey == null) { | |
photoKey = PHOTO_URI | |
} | |
val cr = caller.contentResolver | |
val takenPhotoUri = cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, CONTENT_VALUES) | |
?: throw IllegalStateException("Photo insertion failed") | |
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) | |
intent.putExtra(MediaStore.EXTRA_OUTPUT, getIntentUri(caller, takenPhotoUri, cr)) | |
caller.startActivityForResult(intent, requestCode) | |
val prefs = caller.getSharedPreferences(PHOTO_SHARED_PREFS_NAME, Context.MODE_PRIVATE) | |
prefs.edit().putString(photoKey, takenPhotoUri.toString()).commit() | |
} | |
private fun getIntentUri(context: Context, takenPhotoUri: Uri, cr: ContentResolver): Uri { | |
val path = getPhotoFilePath(takenPhotoUri, cr) | |
?: throw IllegalStateException("Photo resolution failed") | |
return FileProvider.getUriForFile( | |
context, | |
"${BuildConfig.APPLICATION_ID}.provider", //(use your app signature + ".provider" ) | |
File(path)); | |
// return Uri.fromFile(File(path)) | |
} | |
private fun getPhotoFilePath(takenPhotoUri: Uri, cr: ContentResolver): String? { | |
val cursor = cr.query(takenPhotoUri, PROJECTION, null, null, null) | |
var res: String? = null | |
if (cursor != null) { | |
val dataIdx = cursor.getColumnIndex(MediaColumns.DATA) | |
if (dataIdx >= 0 && cursor.moveToFirst()) | |
res = cursor.getString(dataIdx) | |
cursor.close() | |
} | |
return res | |
} | |
/** | |
* Dirty hack for API level <8 to get a top-level public external storage directory where Camera photos should be placed.<br></br> | |
* Empty photo is inserted, path of its parent directory is retrieved and then photo is deleted.<br></br> | |
* If photo cannot be inserted eg. external storage is not mounted, then "DCIM" folder in root of the external storage is used as a fallback. | |
* @param cr [ContentResolver] used to resolve image Uris | |
* @return path to directory where camera app places photos (may be fallback) | |
*/ | |
fun getPhotoDirPath(cr: ContentResolver): String { | |
val fallback = Environment.getExternalStorageDirectory().toString() + "/DCIM" | |
var takenPhotoUri: Uri? = null | |
var photoFilePath: String? = null | |
try { | |
takenPhotoUri = cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, CONTENT_VALUES) | |
if (takenPhotoUri == null) | |
return fallback | |
photoFilePath = getPhotoFilePath(takenPhotoUri, cr) | |
cr.delete(takenPhotoUri, null, null) | |
} catch (ex: Exception) { | |
//igonred | |
} | |
if (photoFilePath == null) | |
return fallback | |
var parent = File(photoFilePath).parent | |
val m = Pattern.compile("/DCIM(/|$)").matcher(parent) | |
if (m.find()) | |
parent = parent.substring(0, m.end()) | |
return parent | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment