Skip to content

Commit

Permalink
Fix camera capture (#73)
Browse files Browse the repository at this point in the history
* Fix default filter constraint

* Remove app name from attrs

* Add JPG to MimeType

* Use OnImageCapturedCallback instead of OnImageSavedCallback and save later

* Disable scoped write storage permission

* Expand wildcard import
  • Loading branch information
kinnerapriyap authored Aug 12, 2020
1 parent 5476e44 commit 5835438
Show file tree
Hide file tree
Showing 9 changed files with 84 additions and 75 deletions.
4 changes: 1 addition & 3 deletions sher-gil/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@
android:name="android.hardware.camera.any"
android:required="false" />

<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<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" />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package com.kinnerapriyap.sugar
import android.app.Application
import android.content.ContentValues
import android.database.Cursor
import android.graphics.Bitmap
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
Expand All @@ -14,6 +16,9 @@ import com.kinnerapriyap.sugar.mediagallery.MediaGalleryHandler
import com.kinnerapriyap.sugar.mediagallery.cell.MediaCellDisplayModel
import com.kinnerapriyap.sugar.mediagallery.cell.MediaCellUpdateModel
import com.kinnerapriyap.sugar.mediagallery.media.MediaGalleryCursorWrapper
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.IOException

class ShergilViewModel(application: Application) : AndroidViewModel(application) {

Expand All @@ -33,7 +38,7 @@ class ShergilViewModel(application: Application) : AndroidViewModel(application)

private var updatedMediaCellPositions: Pair<Int, Int> = Pair(-1, -1)

private var cursor: MutableLiveData<Cursor?> = MutableLiveData<Cursor?>()
private var cursor: MutableLiveData<Cursor?> = MutableLiveData()

fun fetchCursor() {
cursor.postValue(
Expand Down Expand Up @@ -124,4 +129,42 @@ class ShergilViewModel(application: Application) : AndroidViewModel(application)
fun setSelectedAlbumSpinnerName(bucketDisplayName: String?) {
selectedAlbumSpinnerName.value = bucketDisplayName
}

fun insertCameraImage(
fileName: String,
mimeType: String,
bitmap: Bitmap
) {
val contentResolver = getApplication<Application>().contentResolver ?: return
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
put(MediaStore.MediaColumns.MIME_TYPE, mimeType)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
put(MediaStore.MediaColumns.IS_PENDING, 1)
}
}
val pictureContentUri = contentResolver.insert(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
contentValues
) ?: return
contentResolver.openFileDescriptor(pictureContentUri, "w").use { pfd ->
pfd?.let {
try {
val fos = FileOutputStream(it.fileDescriptor)
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos)
fos.flush()
fos.close()
} catch (e: FileNotFoundException) {
e.printStackTrace()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
contentValues.clear()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
contentValues.put(MediaStore.MediaColumns.IS_PENDING, 0)
}
contentResolver.update(pictureContentUri, contentValues, null, null)
}
}
Original file line number Diff line number Diff line change
@@ -1,48 +1,48 @@
package com.kinnerapriyap.sugar.camera

import android.app.Activity
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.webkit.MimeTypeMap
import android.widget.AdapterView
import android.widget.Toast
import androidx.camera.core.Camera
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCapture.Metadata
import androidx.camera.core.ImageCaptureException
import androidx.camera.core.Preview
import androidx.camera.core.ImageCaptureException
import androidx.camera.core.ImageProxy
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.content.ContextCompat
import androidx.core.net.toFile
import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import com.kinnerapriyap.sugar.R
import com.kinnerapriyap.sugar.ShergilActivity
import com.kinnerapriyap.sugar.ShergilViewModel
import com.kinnerapriyap.sugar.databinding.FragmentCameraBinding
import java.io.File

class CameraFragment : Fragment(), CameraUIListener, AdapterView.OnItemSelectedListener {

private val viewModel: ShergilViewModel by activityViewModels()

private var imageCapture: ImageCapture? = null

private var camera: Camera? = null

private var cameraProvider: ProcessCameraProvider? = null

private lateinit var outputDirectory: File

private var binding: FragmentCameraBinding? = null

private var capturedBitmap: Bitmap? = null

private val cameraFlashSpinnerAdapter by lazy {
CameraFlashSpinnerAdapter(requireContext())
}
Expand All @@ -52,8 +52,8 @@ class CameraFragment : Fragment(), CameraUIListener, AdapterView.OnItemSelectedL
private var flashMode: Int = ImageCapture.FLASH_MODE_OFF

companion object {
private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"
private const val PHOTO_EXTENSION = ".jpg"
const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"
const val MIME_TYPE = "image/jpeg"
}

override fun onCreateView(
Expand All @@ -67,8 +67,6 @@ class CameraFragment : Fragment(), CameraUIListener, AdapterView.OnItemSelectedL

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
outputDirectory = getOutputDirectory(activity ?: return)

binding?.isCapture = true
binding?.viewFinder?.post {
setupCamera()
Expand Down Expand Up @@ -105,6 +103,12 @@ class CameraFragment : Fragment(), CameraUIListener, AdapterView.OnItemSelectedL
}

override fun onCameraCaptureYesClicked() {
val bitmap = capturedBitmap ?: return
viewModel.insertCameraImage(
getFileDisplayName(FILENAME_FORMAT),
MIME_TYPE,
bitmap
)
(requireActivity() as? ShergilActivity)?.askPermissionAndOpenGallery()
}

Expand Down Expand Up @@ -172,19 +176,10 @@ class CameraFragment : Fragment(), CameraUIListener, AdapterView.OnItemSelectedL
private fun takePhoto() {
val imageCapture = imageCapture ?: return
val activity = activity ?: return
val photoFile = createFile(outputDirectory, FILENAME_FORMAT, PHOTO_EXTENSION)
val metadata = Metadata().apply {
isReversedHorizontal = lensFacing == CameraSelector.LENS_FACING_FRONT
}
val outputOptions =
ImageCapture.OutputFileOptions.Builder(photoFile)
.setMetadata(metadata)
.build()

imageCapture.takePicture(
outputOptions,
ContextCompat.getMainExecutor(activity),
object : ImageCapture.OnImageSavedCallback {
object : ImageCapture.OnImageCapturedCallback() {
override fun onError(e: ImageCaptureException) {
Toast.makeText(
activity,
Expand All @@ -193,18 +188,11 @@ class CameraFragment : Fragment(), CameraUIListener, AdapterView.OnItemSelectedL
).show()
}

override fun onImageSaved(output: ImageCapture.OutputFileResults) {
val savedUri = Uri.fromFile(photoFile)
val mimeType = MimeTypeMap.getSingleton()
.getMimeTypeFromExtension(savedUri.toFile().extension)
MediaScannerConnection.scanFile(
activity,
arrayOf(savedUri.toFile().absolutePath),
arrayOf(mimeType)
) { _, uri ->
binding?.isCapture = false
binding?.cameraCapturePreviewImage?.setImageURI(uri)
}
override fun onCaptureSuccess(image: ImageProxy) {
capturedBitmap = image.toBitmap()
binding?.isCapture = false
binding?.cameraCapturePreviewImage?.setImageBitmap(capturedBitmap)
super.onCaptureSuccess(image)
}
}
)
Expand Down
45 changes: 14 additions & 31 deletions sher-gil/src/main/java/com/kinnerapriyap/sugar/camera/CameraUtil.kt
Original file line number Diff line number Diff line change
@@ -1,44 +1,19 @@
package com.kinnerapriyap.sugar.camera

import android.app.Activity
import android.content.Context
import android.util.TypedValue
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageProxy
import androidx.camera.lifecycle.ProcessCameraProvider
import com.kinnerapriyap.sugar.R
import java.io.File
import java.text.SimpleDateFormat
import java.util.Locale

const val ANIMATION_FAST_MILLIS = 50L
const val ANIMATION_SLOW_MILLIS = 100L

fun getOutputDirectory(activity: Activity): File {
val dirName = getStringResourceFromAttr(R.attr.shergil_appName, activity)
val mediaDir =
activity.externalMediaDirs.firstOrNull()?.let {
File(it, dirName).apply { mkdirs() }
}
return if (mediaDir != null && mediaDir.exists()) mediaDir else activity.filesDir
}

fun createFile(baseFolder: File, format: String, extension: String) =
File(
baseFolder,
SimpleDateFormat(format, Locale.US)
.format(System.currentTimeMillis()) + extension
)

private fun getStringResourceFromAttr(attribute: Int, context: Context): String {
var isValid: Boolean
val typedValue = TypedValue().apply {
isValid = context.theme.resolveAttribute(attribute, this, true)
}
return context.resources.getString(
if (isValid) typedValue.resourceId
else R.string.shergil
)
}
fun getFileDisplayName(format: String): String =
SimpleDateFormat(format, Locale.ENGLISH)
.format(System.currentTimeMillis())

/**
* Determines whether or not the device has an available back camera
Expand All @@ -51,3 +26,11 @@ fun ProcessCameraProvider?.hasBackCamera(): Boolean =
*/
fun ProcessCameraProvider?.hasFrontCamera(): Boolean =
this?.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) ?: false

fun ImageProxy.toBitmap(): Bitmap {
val buffer = planes[0].buffer
buffer.rewind()
val bytes = ByteArray(buffer.remaining())
buffer.get(bytes)
return BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package com.kinnerapriyap.sugar.choice
enum class MimeType(val value: String) {
// Images
JPEG("image/jpeg"),
JPG("image/jpg"),
GIF("image/gif"),
PNG("image/png"),
BMP("image/x-ms-bmp"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class MediaGalleryFragment : Fragment(), MediaCellListener {
mediaGalleryAdapter.filterQueryProvider = FilterQueryProvider { filter ->
viewModel.getCurrentMediaCursor(filter.toString())
}
mediaGalleryAdapter.filter.filter(null)
mediaGalleryAdapter.filter.filter("")
}
)

Expand Down
2 changes: 0 additions & 2 deletions sher-gil/src/main/res/values/attrs.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="shergil_appName" format="string" />

<!-- CardView -->
<attr name="shergil_cardMediaCheckedColor" format="color" />
<attr name="shergil_cardMediaCheckedAlpha" format="float" />
Expand Down
1 change: 0 additions & 1 deletion sher-gil/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<resources>
<string name="shergil">shergil</string>
<string name="apply">Apply</string>
<string name="preview">Preview</string>
<string name="max_selectable_error">You can only select up to %d media files</string>
Expand Down
1 change: 0 additions & 1 deletion sher-gil/src/main/res/values/styles.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

<!-- Requires your theme to be a descendant of Theme.MaterialComponents -->
<style name="Shergil" parent="Theme.MaterialComponents.Light.NoActionBar">
<item name="shergil_appName">@string/shergil</item>
<item name="android:fontFamily">sans-serif</item>

<!-- CardView -->
Expand Down

0 comments on commit 5835438

Please sign in to comment.