Update dependencies & Add camera capture mode settings

This commit is contained in:
Matéo Duparc 2021-09-01 19:31:25 +02:00
parent ba42938f5a
commit b65ac79175
Signed by untrusted user: hardcoresushi
GPG Key ID: 007F84120107191E
14 changed files with 143 additions and 83 deletions

View File

@ -56,8 +56,8 @@ dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "androidx.core:core-ktx:1.6.0"
implementation "androidx.appcompat:appcompat:1.3.0"
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
implementation "androidx.appcompat:appcompat:1.3.1"
implementation "androidx.constraintlayout:constraintlayout:2.1.0"
implementation "androidx.sqlite:sqlite-ktx:2.1.0"
implementation "androidx.preference:preference-ktx:1.1.1"
@ -66,14 +66,14 @@ dependencies {
implementation "com.github.bumptech.glide:glide:4.12.0"
implementation "androidx.biometric:biometric:1.1.0"
def exoplayer_version = "2.14.1"
def exoplayer_version = "2.15.0"
implementation "com.google.android.exoplayer:exoplayer-core:$exoplayer_version"
implementation "com.google.android.exoplayer:exoplayer-ui:$exoplayer_version"
def camerax_v1 = "1.1.0-alpha06"
def camerax_v1 = "1.1.0-alpha08"
implementation "androidx.camera:camera-camera2:$camerax_v1"
implementation "androidx.camera:camera-lifecycle:$camerax_v1"
def camerax_v2 = "1.0.0-alpha26"
def camerax_v2 = "1.0.0-alpha28"
implementation "androidx.camera:camera-view:$camerax_v2"
implementation "androidx.camera:camera-extensions:$camerax_v2"
}

View File

@ -8,7 +8,6 @@ import android.hardware.camera2.CameraManager
import android.os.Build
import android.os.Bundle
import android.text.InputType
import android.util.DisplayMetrics
import android.util.Size
import android.view.MotionEvent
import android.view.ScaleGestureDetector
@ -23,7 +22,8 @@ import android.widget.RelativeLayout
import android.widget.Toast
import androidx.camera.camera2.interop.Camera2CameraInfo
import androidx.camera.core.*
import androidx.camera.extensions.HdrImageCaptureExtender
import androidx.camera.extensions.ExtensionMode
import androidx.camera.extensions.ExtensionsManager
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.content.ContextCompat
import sushi.hardcore.droidfs.adapters.DialogSingleChoiceAdapter
@ -35,8 +35,7 @@ import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.Executor
class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
companion object {
@ -64,10 +63,16 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
private lateinit var outputDirectory: String
private lateinit var fileName: String
private var isFinishingIntentionally = false
private lateinit var cameraExecutor: ExecutorService
private var permissionsGranted = false
private lateinit var executor: Executor
private lateinit var cameraProvider: ProcessCameraProvider
private lateinit var extensionsManager: ExtensionsManager
private val cameraPreview = Preview.Builder().build()
private var imageCapture: ImageCapture? = null
private var resolutions: Array<Size>? = null
private var camera: Camera? = null
private var resolutions: List<Size>? = null
private var currentResolutionIndex: Int = 0
private var captureMode = ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY
private var isBackCamera = true
private lateinit var binding: ActivityCameraBinding
@ -81,21 +86,61 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED){
setupCamera()
permissionsGranted = true
} else {
requestPermissions(arrayOf(Manifest.permission.CAMERA), CAMERA_PERMISSION_REQUEST_CODE)
}
} else {
setupCamera()
permissionsGranted = true
}
cameraExecutor = Executors.newSingleThreadExecutor()
executor = ContextCompat.getMainExecutor(this)
cameraPreview.setSurfaceProvider(binding.cameraPreview.surfaceProvider)
ProcessCameraProvider.getInstance(this).apply {
addListener({
cameraProvider = get()
setupCamera()
}, executor)
}
ExtensionsManager.getInstance(this).apply {
addListener({
extensionsManager = get()
setupCamera()
}, executor)
}
binding.imageCaptureMode.setOnClickListener {
val currentIndex = if (captureMode == ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY) {
0
} else {
1
}
ColoredAlertDialogBuilder(this)
.setTitle(R.string.camera_optimization)
.setSingleChoiceItems(DialogSingleChoiceAdapter(this, arrayOf(R.string.maximize_quality, R.string.minimize_latency).map { getString(it) }), currentIndex) { dialog, which ->
val resId: Int
val newCaptureMode = if (which == 0) {
resId = R.drawable.icon_high_quality
ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY
} else {
resId = R.drawable.icon_speed
ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY
}
if (newCaptureMode != captureMode) {
captureMode = newCaptureMode
binding.imageCaptureMode.setImageResource(resId)
setupCamera()
}
dialog.dismiss()
}
.setNegativeButton(R.string.cancel, null)
.show()
}
binding.imageRatio.setOnClickListener {
resolutions?.let {
ColoredAlertDialogBuilder(this)
.setTitle(R.string.choose_resolution)
.setSingleChoiceItems(DialogSingleChoiceAdapter(this, it.map { size -> size.toString() }.toTypedArray()), currentResolutionIndex) { dialog, which ->
.setSingleChoiceItems(DialogSingleChoiceAdapter(this, it.map { size -> size.toString() }), currentResolutionIndex) { dialog, which ->
setupCamera(resolutions!![which])
dialog.dismiss()
currentResolutionIndex = which
@ -151,10 +196,10 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
}
binding.imageCameraSwitch.setOnClickListener {
isBackCamera = if (isBackCamera) {
binding.imageCameraSwitch.setImageResource(R.drawable.icon_camera_front)
binding.imageCameraSwitch.setImageResource(R.drawable.icon_camera_back)
false
} else {
binding.imageCameraSwitch.setImageResource(R.drawable.icon_camera_back)
binding.imageCameraSwitch.setImageResource(R.drawable.icon_camera_front)
true
}
setupCamera()
@ -165,8 +210,8 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
val scaleGestureDetector = ScaleGestureDetector(this, object : ScaleGestureDetector.SimpleOnScaleGestureListener(){
override fun onScale(detector: ScaleGestureDetector): Boolean {
val currentZoomRatio = imageCapture?.camera?.cameraInfo?.zoomState?.value?.zoomRatio ?: 0F
imageCapture?.camera?.cameraControl?.setZoomRatio(currentZoomRatio*detector.scaleFactor)
val currentZoomRatio = camera?.cameraInfo?.zoomState?.value?.zoomRatio ?: 0F
camera?.cameraControl?.setZoomRatio(currentZoomRatio*detector.scaleFactor)
return true
}
})
@ -178,7 +223,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
val factory = binding.cameraPreview.meteringPointFactory
val point = factory.createPoint(event.x, event.y)
val action = FocusMeteringAction.Builder(point).build()
imageCapture?.camera?.cameraControl?.startFocusAndMetering(action)
camera?.cameraControl?.startFocusAndMetering(action)
true
}
MotionEvent.ACTION_MOVE -> scaleGestureDetector.onTouchEvent(event)
@ -191,7 +236,10 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
CAMERA_PERMISSION_REQUEST_CODE -> if (grantResults.size == 1) {
if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
permissionsGranted = true
setupCamera()
} else {
ColoredAlertDialogBuilder(this)
.setTitle(R.string.error)
.setMessage(R.string.camera_perm_needed)
@ -200,72 +248,62 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
isFinishingIntentionally = true
finish()
}.show()
} else {
setupCamera()
}
}
}
}
private fun adaptPreviewSize(resolution: Size) {
val metrics = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(metrics)
//resolution.width and resolution.height seem to be inverted
val width = resolution.height
val height = resolution.width
binding.cameraPreview.layoutParams = if (metrics.widthPixels < width){
val screenWidth = resources.displayMetrics.widthPixels
binding.cameraPreview.layoutParams = if (screenWidth < resolution.width) {
RelativeLayout.LayoutParams(
metrics.widthPixels,
(height * (metrics.widthPixels.toFloat() / width)).toInt()
screenWidth,
(resolution.height * (screenWidth.toFloat() / resolution.width)).toInt()
)
} else {
RelativeLayout.LayoutParams(width, height)
RelativeLayout.LayoutParams(resolution.width, resolution.height)
}
(binding.cameraPreview.layoutParams as RelativeLayout.LayoutParams).addRule(RelativeLayout.CENTER_IN_PARENT)
}
private fun setupCamera(resolution: Size? = null){
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder()
.build()
.also {
it.setSurfaceProvider(binding.cameraPreview.surfaceProvider)
if (permissionsGranted && ::extensionsManager.isInitialized && ::cameraProvider.isInitialized) {
imageCapture = ImageCapture.Builder()
.setCaptureMode(captureMode)
.setFlashMode(imageCapture?.flashMode ?: ImageCapture.FLASH_MODE_AUTO)
.apply {
resolution?.let {
setTargetResolution(it)
}
}
val builder = ImageCapture.Builder()
.setFlashMode(ImageCapture.FLASH_MODE_AUTO)
resolution?.let {
builder.setTargetResolution(it)
}
val hdrImageCapture = HdrImageCaptureExtender.create(builder)
val cameraSelector = if (isBackCamera){ CameraSelector.DEFAULT_BACK_CAMERA } else { CameraSelector.DEFAULT_FRONT_CAMERA }
.build()
if (hdrImageCapture.isExtensionAvailable(cameraSelector)){
hdrImageCapture.enableExtension(cameraSelector)
var cameraSelector = if (isBackCamera){ CameraSelector.DEFAULT_BACK_CAMERA } else { CameraSelector.DEFAULT_FRONT_CAMERA }
if (extensionsManager.isExtensionAvailable(cameraProvider, cameraSelector, ExtensionMode.HDR)) {
cameraSelector = extensionsManager.getExtensionEnabledCameraSelector(cameraProvider, cameraSelector, ExtensionMode.HDR)
}
imageCapture = builder.build()
cameraProvider.unbindAll()
val camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture)
camera = cameraProvider.bindToLifecycle(this, cameraSelector, cameraPreview, imageCapture)
adaptPreviewSize(imageCapture!!.attachedSurfaceResolution!!)
adaptPreviewSize(resolution ?: imageCapture!!.attachedSurfaceResolution!!.swap())
val info = Camera2CameraInfo.from(camera.cameraInfo)
val cameraManager = getSystemService(Context.CAMERA_SERVICE) as CameraManager
val characteristics = cameraManager.getCameraCharacteristics(info.cameraId)
characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)?.let { streamConfigurationMap ->
resolutions = streamConfigurationMap.getOutputSizes(imageCapture!!.imageFormat)
if (resolutions == null) {
val info = Camera2CameraInfo.from(camera!!.cameraInfo)
val cameraManager = getSystemService(Context.CAMERA_SERVICE) as CameraManager
val characteristics = cameraManager.getCameraCharacteristics(info.cameraId)
characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)?.let { streamConfigurationMap ->
resolutions = streamConfigurationMap.getOutputSizes(imageCapture!!.imageFormat).map { it.swap() }
}
}
}, ContextCompat.getMainExecutor(this))
}
}
private fun takePhoto() {
val imageCapture = imageCapture ?: return
val outputBuff = ByteArrayOutputStream()
val outputOptions = ImageCapture.OutputFileOptions.Builder(outputBuff).build()
imageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback {
imageCapture.takePicture(outputOptions, executor, object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
binding.takePhotoButton.onPhotoTaken()
if (gocryptfsVolume.importFile(ByteArrayInputStream(outputBuff.toByteArray()), PathUtils.pathJoin(outputDirectory, fileName))){
@ -313,7 +351,6 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
override fun onDestroy() {
super.onDestroy()
cameraExecutor.shutdown()
if (!isFinishingIntentionally) {
gocryptfsVolume.close()
RestrictedFileProvider.wipeAll(this)
@ -365,4 +402,8 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
orientedIcons.map { it.startAnimation(rotateAnimation) }
previousOrientation = reversedOrientation
}
}
private fun Size.swap(): Size {
return Size(height, width)
}

View File

@ -9,7 +9,7 @@ import android.widget.CheckedTextView
import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.widgets.ThemeColor
class DialogSingleChoiceAdapter(private val context: Context, private val entries: Array<String>): BaseAdapter() {
class DialogSingleChoiceAdapter(private val context: Context, private val entries: List<String>): BaseAdapter() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val view: View = convertView ?: inflater.inflate(R.layout.adapter_colored_dialog_single_choice, parent, false)

View File

@ -486,7 +486,7 @@ open class BaseExplorerActivity : BaseActivity() {
R.id.sort -> {
ColoredAlertDialogBuilder(this)
.setTitle(R.string.sort_order)
.setSingleChoiceItems(DialogSingleChoiceAdapter(this, sortOrderEntries), currentSortOrderIndex) { dialog, which ->
.setSingleChoiceItems(DialogSingleChoiceAdapter(this, sortOrderEntries.toList()), currentSortOrderIndex) { dialog, which ->
currentSortOrderIndex = which
setCurrentPath(currentDirectoryPath)
dialog.dismiss()

View File

@ -1,10 +1,7 @@
package sushi.hardcore.droidfs.file_viewers
import android.view.WindowManager
import com.google.android.exoplayer2.ExoPlaybackException
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.SimpleExoPlayer
import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.extractor.ExtractorsFactory
import com.google.android.exoplayer2.extractor.flac.FlacExtractor
import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor
@ -58,15 +55,13 @@ abstract class MediaPlayer: FileViewerActivity() {
onPlayerReady()
}
}
override fun onPlayerError(error: ExoPlaybackException) {
if (error.type == ExoPlaybackException.TYPE_SOURCE){
ColoredAlertDialogBuilder(this@MediaPlayer)
.setTitle(R.string.error)
.setMessage(R.string.playing_failed)
.setCancelable(false)
.setPositiveButton(R.string.ok) { _, _ -> goBackToExplorer()}
.show()
}
override fun onPlayerError(error: PlaybackException) {
ColoredAlertDialogBuilder(this@MediaPlayer)
.setTitle(R.string.error)
.setMessage(getString(R.string.playing_failed, error.errorCodeName))
.setCancelable(false)
.setPositiveButton(R.string.ok) { _, _ -> goBackToExplorer()}
.show()
}
override fun onIsPlayingChanged(isPlaying: Boolean) {
if (isPlaying){

View File

@ -19,7 +19,7 @@ class ColoredListPreference: ListPreference {
override fun onClick() {
ColoredAlertDialogBuilder(context)
.setTitle(title)
.setSingleChoiceItems(DialogSingleChoiceAdapter(context, entries.map { s -> s.toString() }.toTypedArray()), entryValues.indexOf(getPersistedString(value))) { dialog, which ->
.setSingleChoiceItems(DialogSingleChoiceAdapter(context, entries.map { s -> s.toString() }), entryValues.indexOf(getPersistedString(value))) { dialog, which ->
dialog.dismiss()
summary = entries[which].toString()
persistString(entryValues[which].toString())

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19,4L5,4c-1.11,0 -2,0.9 -2,2v12c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,6c0,-1.1 -0.9,-2 -2,-2zM11,15L9.5,15v-2h-2v2L6,15L6,9h1.5v2.5h2L9.5,9L11,9v6zM18,14c0,0.55 -0.45,1 -1,1h-0.75v1.5h-1.5L14.75,15L14,15c-0.55,0 -1,-0.45 -1,-1v-4c0,-0.55 0.45,-1 1,-1h3c0.55,0 1,0.45 1,1v4zM14.5,13.5h2v-3h-2v3z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M20.38,8.57l-1.23,1.85a8,8 0,0 1,-0.22 7.58L5.07,18A8,8 0,0 1,15.58 6.85l1.85,-1.23A10,10 0,0 0,3.35 19a2,2 0,0 0,1.72 1h13.85a2,2 0,0 0,1.74 -1,10 10,0 0,0 -0.27,-10.44zM10.59,15.41a2,2 0,0 0,2.83 0l5.66,-8.49 -8.49,5.66a2,2 0,0 0,0 2.83z"/>
</vector>

View File

@ -17,6 +17,16 @@
android:layout_height="40dp"
android:layout_marginTop="20dp">
<ImageView
android:id="@+id/image_capture_mode"
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@drawable/icon_high_quality"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/image_ratio"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<ImageView
android:id="@+id/image_ratio"
android:layout_width="30dp"
@ -24,7 +34,7 @@
android:src="@drawable/icon_aspect_ratio"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/image_timer"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintStart_toEndOf="@id/image_capture_mode"
app:layout_constraintTop_toTopOf="parent" />
<ImageView

View File

@ -116,7 +116,7 @@
<string name="video">Vídeo</string>
<string name="audio">Áudio</string>
<string name="media_player_prepare_failed">Falha ao abrir o reprodutor de mídia.</string>
<string name="playing_failed">Falha ao reproduzir este arquivo.</string>
<string name="playing_failed">Falha ao reproduzir este arquivo: %s</string>
<string name="text">Texto</string>
<string name="save_failed">Falha ao salvar</string>
<string name="file_saved">Arquivo salvo!</string>

View File

@ -117,7 +117,7 @@
<string name="video">Видео</string>
<string name="audio">Аудио</string>
<string name="media_player_prepare_failed">Невозможно инициализировать медиапроигрыватель.</string>
<string name="playing_failed">Невозможно воспроизвести файл.</string>
<string name="playing_failed">Невозможно воспроизвести файл: %s</string>
<string name="text">Текст</string>
<string name="save_failed">Невозможно сохранить.</string>
<string name="file_saved">Файл сохранён!</string>

View File

@ -118,7 +118,7 @@
<string name="video">Video</string>
<string name="audio">Audio</string>
<string name="media_player_prepare_failed">Failed to initialize the media player.</string>
<string name="playing_failed">Failed to play this file.</string>
<string name="playing_failed">Failed to play this file: %s</string>
<string name="text">Text</string>
<string name="save_failed">Save failed</string>
<string name="file_saved">File saved !</string>
@ -202,4 +202,7 @@
<string name="cut">Cut</string>
<string name="map_folders">Map folders</string>
<string name="map_folders_summary">Recursively map folders to calculate their sizes (you should disable this when opening large volumes)</string>
<string name="camera_optimization">Camera optimization</string>
<string name="maximize_quality">Maximize quality</string>
<string name="minimize_latency">Minimize latency</string>
</resources>

View File

@ -1,6 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = "1.5.20"
ext.kotlin_version = "1.5.30"
repositories {
google()
mavenCentral()

View File

@ -1,5 +1,6 @@
#Wed Sep 01 11:25:55 UTC 2021
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME