Created
July 2, 2021 09:16
-
-
Save shohiebsense/121a68205b51413ebe8136cecd384eb9 to your computer and use it in GitHub Desktop.
CameraX for targeting SDK 30/Q, using ContentResolver instead of File.
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"?> | |
<androidx.constraintlayout.widget.ConstraintLayout | |
xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:tools="http://schemas.android.com/tools" | |
xmlns:app="http://schemas.android.com/apk/res-auto" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
tools:context=".MainActivity"> | |
<Button | |
android:id="@+id/camera_capture_button" | |
android:layout_width="100dp" | |
android:layout_height="100dp" | |
android:layout_marginBottom="50dp" | |
android:scaleType="fitCenter" | |
android:text="Take Photo" | |
app:layout_constraintLeft_toLeftOf="parent" | |
app:layout_constraintRight_toRightOf="parent" | |
app:layout_constraintBottom_toBottomOf="parent" | |
android:elevation="2dp" /> | |
<androidx.camera.view.PreviewView | |
android:id="@+id/viewFinder" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" /> | |
</androidx.constraintlayout.widget.ConstraintLayout> |
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
<manifest> | |
<uses-feature android:name="android.hardware.camera.any" /> | |
<uses-permission android:name="android.permission.CAMERA" /> | |
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> | |
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> | |
<application> | |
</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
... | |
dependencies { | |
.... | |
def camerax_version = "1.0.0" | |
implementation "androidx.camera:camera-camera2:$camerax_version" | |
implementation "androidx.camera:camera-lifecycle:$camerax_version" | |
implementation "androidx.camera:camera-view:1.0.0-alpha26" | |
} |
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 com.shohiebsense.cameracompressedsample | |
import android.Manifest | |
import android.content.ContentValues | |
import android.content.Context | |
import android.content.pm.PackageManager | |
import android.hardware.camera2.CameraManager | |
import androidx.appcompat.app.AppCompatActivity | |
import android.os.Bundle | |
import android.os.Environment | |
import android.provider.MediaStore | |
import android.util.Log | |
import android.widget.Toast | |
import androidx.camera.core.* | |
import androidx.camera.lifecycle.ProcessCameraProvider | |
import androidx.core.app.ActivityCompat | |
import androidx.core.content.ContextCompat | |
import kotlinx.android.synthetic.main.activity_main.* | |
import java.io.File | |
import java.lang.Exception | |
import java.util.concurrent.ExecutorService | |
import java.util.concurrent.Executors | |
class MainActivity : AppCompatActivity() { | |
private var imageCapture: ImageCapture? = null | |
private lateinit var outputDirectory: File | |
private lateinit var cameraExecutor: ExecutorService | |
companion object { | |
private const val TAG = "CameraXBasic" | |
private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" | |
private const val REQUEST_CODE_PERMISSIONS = 10 | |
private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE) | |
} | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
setContentView(R.layout.activity_main) | |
if(allPermissionsGranted()){ | |
startCamera() | |
}else { | |
ActivityCompat.requestPermissions( | |
this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS) | |
} | |
camera_capture_button.setOnClickListener { capturePhoto() } | |
outputDirectory = getOutputDirectory() | |
cameraExecutor = Executors.newSingleThreadExecutor() | |
} | |
private fun startCamera() { | |
if(!isHavingCamera()){ | |
//todo add UI MESSAGE | |
return | |
} | |
val cameraProviderFuture = ProcessCameraProvider.getInstance(this) | |
cameraProviderFuture.addListener(Runnable { | |
val cameraProvider = cameraProviderFuture.get() | |
val preview = Preview.Builder().build().also { | |
it.setSurfaceProvider(viewFinder.surfaceProvider) | |
} | |
imageCapture = ImageCapture.Builder() | |
.build() | |
val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA | |
try { | |
cameraProvider.unbindAll() | |
cameraProvider.bindToLifecycle( | |
this, cameraSelector, preview, imageCapture | |
) | |
} catch (exc: Exception){ | |
Log.e(TAG,"Binding failed", exc) | |
} | |
}, ContextCompat.getMainExecutor(this)) | |
} | |
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all { | |
ContextCompat.checkSelfPermission( | |
baseContext, it) == PackageManager.PERMISSION_GRANTED | |
} | |
override fun onRequestPermissionsResult( | |
requestCode: Int, permissions: Array<String>, grantResults: | |
IntArray) { | |
super.onRequestPermissionsResult(requestCode, permissions, grantResults) | |
if (requestCode == REQUEST_CODE_PERMISSIONS) { | |
if (allPermissionsGranted()) { | |
startCamera() | |
} else { | |
Toast.makeText(this, | |
"Permissions not granted by the user.", | |
Toast.LENGTH_SHORT).show() | |
finish() | |
} | |
} | |
} | |
fun isHavingCamera() : Boolean { | |
val cameraManager = getSystemService(Context.CAMERA_SERVICE) as CameraManager | |
val mCameraIds = cameraManager.cameraIdList | |
return mCameraIds.isNotEmpty() | |
} | |
private fun capturePhoto(){ | |
val imageCapture = imageCapture ?: return | |
val resolver = applicationContext.contentResolver | |
val newSongDetails = ContentValues().apply { | |
put(MediaStore.Images.Media.DISPLAY_NAME, "abcd.jpg") | |
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg") | |
put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/CameraCompressedSample") | |
put(MediaStore.Images.Media.IS_PENDING, true) | |
} | |
val outputOptions = ImageCapture.OutputFileOptions.Builder(resolver, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, newSongDetails).build() | |
imageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback { | |
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { | |
val msg = "Photo capture succeeded: ${outputFileResults.savedUri?.path}" | |
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() | |
Log.d(TAG, msg) | |
} | |
override fun onError(exception: ImageCaptureException) { | |
Log.e(TAG, "Photo capture failed: ${exception.message}", exception) | |
} | |
}) | |
} | |
private fun getOutputDirectory() : File { | |
//TODO handle below ANDROID Q | |
//https://stackoverflow.com/questions/56468539/getexternalstoragepublicdirectory-deprecated-in-android-q | |
val mediaDir = externalMediaDirs.firstOrNull()?.let { | |
File(it, resources.getString(R.string.app_name)).apply { mkdirs() } } | |
return if (mediaDir != null && mediaDir.exists()) | |
mediaDir else filesDir | |
} | |
override fun onDestroy() { | |
super.onDestroy() | |
cameraExecutor.shutdown() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment