Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save snooplsm/d4480fe02fbffd82eaaa27969aac1e06 to your computer and use it in GitHub Desktop.

Select an option

Save snooplsm/d4480fe02fbffd82eaaa27969aac1e06 to your computer and use it in GitHub Desktop.
package cab.reported.nyc.ui.phototaker
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.location.Location
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.Bundle
import android.os.Environment
import android.provider.MediaStore
import android.support.design.widget.BottomSheetDialogFragment
import android.support.media.ExifInterface
import android.support.v4.content.FileProvider
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import cab.reported.nyc.R
import cab.reported.nyc.ReportedSchedulers
import cab.reported.nyc.support.adapter.MediaUtil
import com.crashlytics.android.Crashlytics
import io.reactivex.Single
import kotlinx.android.synthetic.main.fragment_photo_taker_bottom_sheet.*
import java.io.File
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.*
import kotlin.math.roundToInt
/**
* capture: @see https://developer.android.com/training/camera/photobasics.html
* document pick: @see https://developer.android.com/guide/topics/providers/document-provider.html
*/
class PhotoTakerBottomSheetDialog : BottomSheetDialogFragment() {
var listener: PhotoTakerBottomListener? = null
companion object {
const val REQUEST_IMAGE_CAPTURE = 100
const val REQUEST_VIDEO_CAPTURE = 101
const val REQUEST_PICK_MEDIA = 102
}
var takePhoto: Boolean = false
var currentMediaPath: File? = null
override fun onAttach(context: Context?) {
super.onAttach(context)
listener = parentFragment as? PhotoTakerBottomListener
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_photo_taker_bottom_sheet, container, false);
}
@Throws(IOException::class)
private fun createMediaFile(fileExtension: String = "jpg"): File {
// Create an image file name
val timeStamp = SimpleDateFormat("yyyy_MM_dd_HH_mm_ss").format(Date())
val imageFileName = timeStamp + "_"
val storageDir = context?.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
val media = File.createTempFile(
imageFileName, /* prefix */
".$fileExtension", /* suffix */
storageDir /* directory */
)
currentMediaPath = media.absoluteFile
return media
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val str = getString(R.string.authority_file_provider)
photo_taker_take_photo.setOnClickListener {
takePhoto()
}
photo_taker_take_video.setOnClickListener {
takeVideo()
}
photo_taker_select_photo.setOnClickListener {
pickMedia()
}
}
private fun takePhoto() {
val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
if (takePictureIntent.resolveActivity(context?.packageManager) != null) {
val photoFile = createMediaFile()
val photoURI = FileProvider.getUriForFile(context!!,
getString(R.string.authority_file_provider),
photoFile)
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
}
}
private fun pickMedia() {
val mediaChooser = Intent(Intent.ACTION_GET_CONTENT)
mediaChooser.type = "image/*"
mediaChooser.putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("image/*", "video/*"))
startActivityForResult(mediaChooser, REQUEST_PICK_MEDIA)
}
private fun takeVideo() {
val takePictureIntent = Intent(MediaStore.ACTION_VIDEO_CAPTURE)
if (takePictureIntent.resolveActivity(context?.packageManager) != null) {
val photoFile = createMediaFile("mp4")
val photoURI = FileProvider.getUriForFile(context!!,
getString(R.string.authority_file_provider),
photoFile)
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(takePictureIntent, REQUEST_VIDEO_CAPTURE)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (resultCode == Activity.RESULT_OK) {
true -> {
if (requestCode == REQUEST_IMAGE_CAPTURE) {
captureImage()
}
if (requestCode == REQUEST_VIDEO_CAPTURE) {
captureVideo()
}
if (requestCode == REQUEST_PICK_MEDIA) {
capturePick(data)
}
}
}
}
@SuppressLint("CheckResult")
private fun captureImage() {
Single
.just(currentMediaPath)
.compose(ReportedSchedulers.singleDefault())
.map {
val bmOptions = BitmapFactory.Options()
bmOptions.inJustDecodeBounds = true
BitmapFactory.decodeFile(currentMediaPath?.absolutePath, bmOptions)
val photoW = bmOptions.outWidth
val photoH = bmOptions.outHeight
val scaleFactor: Int
scaleFactor = Math.min(photoW / 2048, photoH / 2048)
if (scaleFactor != 1) {
bmOptions.inJustDecodeBounds = false
bmOptions.inSampleSize = scaleFactor
val image = BitmapFactory.decodeFile(currentMediaPath?.absolutePath, bmOptions)
image.compress(Bitmap.CompressFormat.JPEG, 80, currentMediaPath?.outputStream())
image.recycle()
}
}
.map { ExifInterface(currentMediaPath?.absolutePath!!) }
.subscribe({ exif ->
listener?.onPhoto(currentMediaPath!!, exif)
dismiss()
})
}
private fun captureVideo() {
listener?.onVideo(currentMediaPath!!, null)
dismiss()
}
@SuppressLint("CheckResult")
private fun capturePick(intent: Intent?) {
intent?.let {
val contentType = context?.contentResolver?.getType(it.data) ?: ""
if (MediaUtil.isPhoto(contentType)) {
Single
.just(intent.data)
.compose(ReportedSchedulers.singleDefault())
.map {
val file: File = copyFile(it)
Pair(file, ExifInterface(file.absolutePath))
}
.subscribe({ v ->
listener?.onPhoto(v.first, v.second)
dismiss()
}, {
dismiss()
})
} else {
Single.just(intent?.data)
.compose(ReportedSchedulers.singleDefault())
.map {
val file = copyFile(it)
val mmr = MediaMetadataRetriever()
mmr.setDataSource(file.absolutePath)
val keys = arrayOf(MediaMetadataRetriever.METADATA_KEY_DATE, MediaMetadataRetriever.METADATA_KEY_LOCATION)
val keyMap = mutableMapOf<Int,Any?>()
keys.forEach {
val data = mmr.extractMetadata(it) ?: return@forEach
when(it) {
MediaMetadataRetriever.METADATA_KEY_DATE -> {
var date = try {
MediaUtil.getExifDateTime(data)
} catch (e: Exception) {
null
}
if(date!=null) {
val oneyearprior = Calendar.getInstance()
oneyearprior.add(Calendar.YEAR,-1)
val oneyearlater = Calendar.getInstance()
oneyearprior.add(Calendar.YEAR,1)
if(date.before(oneyearprior.time) || date.after(oneyearlater.time)) {
date = null
}
}
keyMap[it] = date
}
MediaMetadataRetriever.METADATA_KEY_LOCATION-> {
val builder = StringBuilder()
data.forEachIndexed { index, c ->
if(index!=0 && c=='+' || c=='-') {
builder.append(',')
}
if(c!='+' && c!='/') {
builder.append(c)
}
}
val splits = builder.toString().split(",")
val lat = java.lang.Double.valueOf(splits[0])
val lon = java.lang.Double.valueOf(splits[1])
val loc = Location("exif").apply {
latitude = lat
longitude = lon
}
keyMap[it] = loc
}
else-> {
keyMap[it] = mmr.extractMetadata(it)
}
}
}
mmr.release()
Pair(file, keyMap)
}
.doOnSuccess({
dismiss()
})
.subscribe({ v ->
listener?.onVideo(v.first, v.second)
}, { e ->
})
}
}
}
@Throws(IOException::class)
private fun copyFile(uri: Uri): File {
val type = context?.contentResolver?.getType(uri)
val parcelFileDescriptor = context?.contentResolver?.openInputStream(uri)
Crashlytics
.setString("type", type)
Crashlytics.setString("uri", uri.toString())
val newFile = createMediaFile(
when (type?.startsWith("video")) {
true -> {
"mp4"
}
false -> {
"jpg"
}
null -> {
"jpg"
}
})
parcelFileDescriptor
?.copyTo(newFile.outputStream())
if (newFile.extension == "jpg") {
val bmOptions = BitmapFactory.Options()
bmOptions.inJustDecodeBounds = true
BitmapFactory.decodeFile(newFile.absolutePath, bmOptions)
val photoW = bmOptions.outWidth
val photoH = bmOptions.outHeight
val scaleFactor = Math.max(Math.ceil(photoW / 2048.0), Math.ceil(photoH / 2048.0)).toInt()
if (scaleFactor != 1) {
bmOptions.inJustDecodeBounds = false
bmOptions.inSampleSize = scaleFactor
val image = BitmapFactory.decodeFile(newFile.absolutePath, bmOptions)
image.compress(Bitmap.CompressFormat.JPEG, 80, newFile.outputStream())
image.recycle()
}
}
Crashlytics.setLong("fileLength", newFile.length())
Crashlytics.log("copyFile")
return newFile
}
interface PhotoTakerBottomListener {
fun onPhoto(file: File, exifInterface: ExifInterface)
fun onVideo(file: File, metaData: Map<Int,Any?>?)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment