From b65ac791756f42cf692eed6f8f2e0849d9c5b02b Mon Sep 17 00:00:00 2001 From: Hardcore Sushi Date: Wed, 1 Sep 2021 19:31:25 +0200 Subject: [PATCH] Update dependencies & Add camera capture mode settings --- app/build.gradle | 10 +- .../sushi/hardcore/droidfs/CameraActivity.kt | 151 +++++++++++------- .../adapters/DialogSingleChoiceAdapter.kt | 2 +- .../droidfs/explorers/BaseExplorerActivity.kt | 2 +- .../droidfs/file_viewers/MediaPlayer.kt | 21 +-- .../droidfs/widgets/ColoredListPreference.kt | 2 +- .../main/res/drawable/icon_high_quality.xml | 5 + app/src/main/res/drawable/icon_speed.xml | 5 + app/src/main/res/layout/activity_camera.xml | 12 +- app/src/main/res/values-pt-rBR/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values/strings.xml | 5 +- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 5 +- 14 files changed, 143 insertions(+), 83 deletions(-) create mode 100644 app/src/main/res/drawable/icon_high_quality.xml create mode 100644 app/src/main/res/drawable/icon_speed.xml diff --git a/app/build.gradle b/app/build.gradle index d3b7ef1..34b8ae7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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" } diff --git a/app/src/main/java/sushi/hardcore/droidfs/CameraActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/CameraActivity.kt index a848e35..89c2a36 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/CameraActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/CameraActivity.kt @@ -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? = null + private var camera: Camera? = null + private var resolutions: List? = 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) } \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/adapters/DialogSingleChoiceAdapter.kt b/app/src/main/java/sushi/hardcore/droidfs/adapters/DialogSingleChoiceAdapter.kt index ce023f3..76e5cdb 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/adapters/DialogSingleChoiceAdapter.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/adapters/DialogSingleChoiceAdapter.kt @@ -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): BaseAdapter() { +class DialogSingleChoiceAdapter(private val context: Context, private val entries: List): 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) diff --git a/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt index f063f20..7b6aa0f 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt @@ -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() diff --git a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/MediaPlayer.kt b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/MediaPlayer.kt index fa7b58f..3a912d5 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/MediaPlayer.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/MediaPlayer.kt @@ -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){ diff --git a/app/src/main/java/sushi/hardcore/droidfs/widgets/ColoredListPreference.kt b/app/src/main/java/sushi/hardcore/droidfs/widgets/ColoredListPreference.kt index 9a51609..f0495fd 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/widgets/ColoredListPreference.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/widgets/ColoredListPreference.kt @@ -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()) diff --git a/app/src/main/res/drawable/icon_high_quality.xml b/app/src/main/res/drawable/icon_high_quality.xml new file mode 100644 index 0000000..2fbf647 --- /dev/null +++ b/app/src/main/res/drawable/icon_high_quality.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/icon_speed.xml b/app/src/main/res/drawable/icon_speed.xml new file mode 100644 index 0000000..1add8a0 --- /dev/null +++ b/app/src/main/res/drawable/icon_speed.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/activity_camera.xml b/app/src/main/res/layout/activity_camera.xml index 7915427..3d92ffd 100644 --- a/app/src/main/res/layout/activity_camera.xml +++ b/app/src/main/res/layout/activity_camera.xml @@ -17,6 +17,16 @@ android:layout_height="40dp" android:layout_marginTop="20dp"> + + Vídeo Áudio Falha ao abrir o reprodutor de mídia. - Falha ao reproduzir este arquivo. + Falha ao reproduzir este arquivo: %s Texto Falha ao salvar Arquivo salvo! diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index f407516..48772f1 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -117,7 +117,7 @@ Видео Аудио Невозможно инициализировать медиапроигрыватель. - Невозможно воспроизвести файл. + Невозможно воспроизвести файл: %s Текст Невозможно сохранить. Файл сохранён! diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cc782c2..d0d0764 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -118,7 +118,7 @@ Video Audio Failed to initialize the media player. - Failed to play this file. + Failed to play this file: %s Text Save failed File saved ! @@ -202,4 +202,7 @@ Cut Map folders Recursively map folders to calculate their sizes (you should disable this when opening large volumes) + Camera optimization + Maximize quality + Minimize latency diff --git a/build.gradle b/build.gradle index 8dc2dec..f95ca1b 100644 --- a/build.gradle +++ b/build.gradle @@ -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() diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 29e4134..fbca759 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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