Skip to content

Commit a385898

Browse files
authored
Merge branch 'GrapheneOS:main' into improve_gyro
2 parents d1fc0bd + b664d89 commit a385898

19 files changed

+800
-496
lines changed

app/build.gradle.kts

+4-4
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,16 @@ android {
4040
}
4141

4242
compileSdk = 34
43-
buildToolsVersion = "34.0.0"
44-
ndkVersion = "26.1.10909125"
43+
buildToolsVersion = "35.0.0"
44+
ndkVersion = "26.3.11579264"
4545

4646
namespace = "app.grapheneos.camera"
4747

4848
defaultConfig {
4949
applicationId = "app.grapheneos.camera"
5050
minSdk = 29
5151
targetSdk = 34
52-
versionCode = 69
52+
versionCode = 71
5353
versionName = versionCode.toString()
5454
resourceConfigurations.add("en")
5555
}
@@ -96,7 +96,7 @@ dependencies {
9696
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
9797
implementation("androidx.core:core:1.13.1")
9898

99-
val cameraVersion = "1.4.0-beta01"
99+
val cameraVersion = "1.4.0-beta02"
100100
implementation("androidx.camera:camera-core:$cameraVersion")
101101
implementation("androidx.camera:camera-camera2:$cameraVersion")
102102
implementation("androidx.camera:camera-lifecycle:$cameraVersion")

app/src/main/java/app/grapheneos/camera/CamConfig.kt

+66-26
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import android.annotation.SuppressLint
44
import android.app.AlertDialog
55
import android.content.Context
66
import android.content.SharedPreferences
7-
import android.hardware.camera2.CameraMetadata
8-
import android.hardware.camera2.CaptureRequest
97
import android.net.Uri
108
import android.os.Build
119
import android.provider.MediaStore
@@ -18,12 +16,13 @@ import android.view.animation.Animation
1816
import android.view.animation.LinearInterpolator
1917
import android.widget.Button
2018
import androidx.annotation.StringRes
21-
import androidx.camera.camera2.interop.Camera2Interop
2219
import androidx.camera.core.AspectRatio
2320
import androidx.camera.core.Camera
21+
import androidx.camera.core.CameraInfo
2422
import androidx.camera.core.CameraSelector
2523
import androidx.camera.core.ImageAnalysis
2624
import androidx.camera.core.ImageCapture
25+
import androidx.camera.core.MirrorMode
2726
import androidx.camera.core.Preview
2827
import androidx.camera.core.TorchState
2928
import androidx.camera.core.UseCaseGroup
@@ -91,6 +90,7 @@ class CamConfig(private val mActivity: MainActivity) {
9190
const val SCAN = "scan"
9291
const val SCAN_ALL_CODES = "scan_all_codes"
9392
const val SAVE_IMAGE_AS_PREVIEW = "save_image_as_preview"
93+
const val SAVE_VIDEO_AS_PREVIEW = "save_video_as_preview"
9494

9595
const val STORAGE_LOCATION = "storage_location"
9696
const val PREVIOUS_SAF_TREES = "previous_saf_trees"
@@ -140,6 +140,8 @@ class CamConfig(private val mActivity: MainActivity) {
140140

141141
const val SAVE_IMAGE_AS_PREVIEW = false
142142

143+
const val SAVE_VIDEO_AS_PREVIEW = false
144+
143145
const val STORAGE_LOCATION = ""
144146

145147
const val PHOTO_QUALITY = 0
@@ -218,6 +220,10 @@ class CamConfig(private val mActivity: MainActivity) {
218220

219221
var lastCapturedItem: CapturedItem? = null
220222

223+
private var frontCameraInfo : CameraInfo? = null
224+
225+
private var rearCameraInfo : CameraInfo? = null
226+
221227
init {
222228
if (mActivity !is SecureActivity) {
223229
CapturedItems.init(mActivity, this)
@@ -429,7 +435,7 @@ class CamConfig(private val mActivity: MainActivity) {
429435

430436
var enableEIS: Boolean
431437
get() {
432-
return mActivity.settingsDialog.enableEISToggle.isChecked
438+
return isStabilizationSupported() && mActivity.settingsDialog.enableEISToggle.isChecked
433439
}
434440
set(value) {
435441
val editor = commonPref.edit()
@@ -465,6 +471,19 @@ class CamConfig(private val mActivity: MainActivity) {
465471
editor.apply()
466472
}
467473

474+
var saveVideoAsPreviewed: Boolean
475+
get() {
476+
return commonPref.getBoolean(
477+
SettingValues.Key.SAVE_VIDEO_AS_PREVIEW,
478+
SettingValues.Default.SAVE_VIDEO_AS_PREVIEW
479+
)
480+
}
481+
set(value) {
482+
val editor = commonPref.edit()
483+
editor.putBoolean(SettingValues.Key.SAVE_VIDEO_AS_PREVIEW, value)
484+
editor.apply()
485+
}
486+
468487
var storageLocation: String
469488
get() {
470489
return commonPref.getString(
@@ -532,6 +551,10 @@ class CamConfig(private val mActivity: MainActivity) {
532551
camera!!.cameraInfo.isZslSupported
533552
}
534553

554+
fun isStabilizationSupported() : Boolean {
555+
return Recorder.getVideoCapabilities(getCurrentCameraInfo()).isStabilizationSupported
556+
}
557+
535558
fun shouldShowGyroscope(): Boolean {
536559
return isInPhotoMode && gSuggestions
537560
}
@@ -894,6 +917,11 @@ class CamConfig(private val mActivity: MainActivity) {
894917
startCamera(true)
895918
}
896919

920+
private fun getCurrentCameraInfo() : CameraInfo {
921+
return if (lensFacing == CameraSelector.LENS_FACING_BACK) rearCameraInfo!!
922+
else frontCameraInfo!!
923+
}
924+
897925
fun toggleCameraSelector() {
898926

899927
// Manually switch to the opposite lens facing
@@ -937,6 +965,12 @@ class CamConfig(private val mActivity: MainActivity) {
937965
return
938966
}
939967

968+
// Select a single camera for front/rear facing
969+
for (cameraInfo in cameraProvider!!.availableCameraInfos) {
970+
if (cameraInfo.lensFacing == CameraSelector.LENS_FACING_FRONT) frontCameraInfo = cameraInfo
971+
else if (cameraInfo.lensFacing == CameraSelector.LENS_FACING_BACK) rearCameraInfo = cameraInfo
972+
}
973+
940974
// Manually switch to the other lens facing (if the default lens facing isn't
941975
// supported for the current device)
942976
if (!isLensFacingSupported(lensFacing)) {
@@ -963,11 +997,11 @@ class CamConfig(private val mActivity: MainActivity) {
963997
}
964998

965999
private fun isLensFacingSupported(lensFacing : Int) : Boolean {
966-
val tCameraSelector = CameraSelector.Builder()
967-
.requireLensFacing(lensFacing)
968-
.build()
969-
970-
return cameraProvider?.hasCamera(tCameraSelector) ?: false
1000+
return when(lensFacing) {
1001+
CameraSelector.LENS_FACING_FRONT -> frontCameraInfo != null
1002+
CameraSelector.LENS_FACING_BACK -> rearCameraInfo != null
1003+
else -> false
1004+
}
9711005
}
9721006

9731007
// Start the camera with latest hard configuration
@@ -993,6 +1027,15 @@ class CamConfig(private val mActivity: MainActivity) {
9931027

9941028
cameraSelector = CameraSelector.Builder()
9951029
.requireLensFacing(lensFacing)
1030+
.addCameraFilter {
1031+
return@addCameraFilter listOf(
1032+
if (lensFacing == CameraSelector.LENS_FACING_BACK) {
1033+
rearCameraInfo
1034+
} else {
1035+
frontCameraInfo
1036+
}
1037+
)
1038+
}
9961039
.build()
9971040

9981041
val builder = ImageCapture.Builder()
@@ -1053,12 +1096,18 @@ class CamConfig(private val mActivity: MainActivity) {
10531096
View.VISIBLE
10541097
}
10551098

1056-
videoCapture =
1057-
VideoCapture.withOutput(
1058-
Recorder.Builder()
1059-
.setQualitySelector(QualitySelector.from(videoQuality))
1060-
.build()
1061-
)
1099+
val videoCaptureBuilder = VideoCapture.Builder(
1100+
Recorder.Builder()
1101+
.setQualitySelector(QualitySelector.from(videoQuality))
1102+
.build()
1103+
)
1104+
1105+
videoCaptureBuilder.setVideoStabilizationEnabled(mActivity.camConfig.enableEIS)
1106+
1107+
if (mActivity.camConfig.saveVideoAsPreviewed)
1108+
videoCaptureBuilder.setMirrorMode(MirrorMode.MIRROR_MODE_ON_FRONT_ONLY)
1109+
1110+
videoCapture = videoCaptureBuilder.build()
10621111

10631112
useCaseGroupBuilder.addUseCase(videoCapture!!)
10641113
}
@@ -1110,17 +1159,8 @@ class CamConfig(private val mActivity: MainActivity) {
11101159
ResolutionSelector.Builder().setAspectRatioStrategy(aspectRatioStrategy).build()
11111160
)
11121161

1113-
@androidx.camera.camera2.interop.ExperimentalCamera2Interop
1114-
if (isVideoMode && enableEIS) {
1115-
Camera2Interop.Extender(previewBuilder).setCaptureRequestOption(
1116-
CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE,
1117-
CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_ON
1118-
)
1119-
} else {
1120-
Camera2Interop.Extender(previewBuilder).setCaptureRequestOption(
1121-
CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE,
1122-
CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_OFF
1123-
)
1162+
if (isVideoMode) {
1163+
previewBuilder.setPreviewStabilizationEnabled(enableEIS)
11241164
}
11251165

11261166
preview = previewBuilder.build().also {

app/src/main/java/app/grapheneos/camera/GallerySliderAdapter.kt

-2
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,6 @@ class GallerySliderAdapter(
8888
intent.putExtra(VideoPlayer.IN_SECURE_MODE, gActivity.isSecureMode)
8989

9090
gActivity.startActivity(intent)
91-
} else {
92-
gActivity.toggleActionBarState()
9391
}
9492
}
9593
} else {

app/src/main/java/app/grapheneos/camera/capturer/VideoCapturer.kt

+37-35
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import java.util.Date
3838
import java.util.Locale
3939

4040
class VideoCapturer(private val mActivity: MainActivity) {
41+
4142
val camConfig = mActivity.camConfig
4243

4344
var isRecording = false
@@ -47,53 +48,37 @@ class VideoCapturer(private val mActivity: MainActivity) {
4748

4849
private var recording: Recording? = null
4950

51+
var includeAudio: Boolean = false
52+
5053
var isPaused = false
5154
set(value) {
5255
if (isRecording) {
5356
if (value) {
5457
recording?.pause()
55-
pauseTimer()
5658
mActivity.flipCamIcon.setImageResource(R.drawable.play)
5759
} else {
5860
recording?.resume()
59-
startTimer()
6061
mActivity.flipCamIcon.setImageResource(R.drawable.pause)
6162
}
6263
}
6364
field = value
6465
}
6566

6667
private val handler = Handler(Looper.getMainLooper())
67-
private var elapsedSeconds: Int = 0
68-
private val runnable = Runnable {
69-
++elapsedSeconds
70-
val secs = padTo2(elapsedSeconds % 60)
71-
val min = padTo2(elapsedSeconds / 60 % 60)
72-
val hours = padTo2(elapsedSeconds / 3600)
73-
val timerText: String = if (hours == "00") {
74-
"$min:$secs"
75-
} else {
76-
"$hours:$min:$secs"
77-
}
78-
mActivity.timerView.text = timerText
79-
startTimer()
80-
}
81-
82-
private fun padTo2(time: Int): String {
83-
return String.format("%1$" + 2 + "s", time).replace(' ', '0')
84-
}
8568

86-
private fun startTimer() {
87-
handler.postDelayed(runnable, 1000)
88-
}
69+
private fun updateTimerTime(timeInNanos: Long) {
70+
val timeInSec = timeInNanos / (1000 * 1000 * 1000)
71+
val sec = timeInSec % 60
72+
val min = timeInSec / 60 % 60
73+
val hour = timeInSec / 3600
8974

90-
private fun cancelTimer() {
91-
elapsedSeconds = 0
92-
handler.removeCallbacks(runnable)
93-
}
75+
val timerText: String = if (hour == 0L) {
76+
String.format(Locale.ROOT, "%02d:%02d", min, sec)
77+
} else {
78+
String.format(Locale.ROOT, "%02d:%02d:%02d", hour, min, sec)
79+
}
9480

95-
private fun pauseTimer() {
96-
handler.removeCallbacks(runnable)
81+
mActivity.timerView.text = timerText
9782
}
9883

9984
private class RecordingContext(
@@ -166,7 +151,7 @@ class VideoCapturer(private val mActivity: MainActivity) {
166151
val dateString = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
167152
val fileName = VIDEO_NAME_PREFIX + dateString + videoFileFormat
168153

169-
var includeAudio = false
154+
includeAudio = false
170155

171156
val ctx = mActivity
172157

@@ -197,10 +182,21 @@ class VideoCapturer(private val mActivity: MainActivity) {
197182
}
198183

199184
beforeRecordingStarts()
185+
200186
isRecording = true
187+
201188
camConfig.mPlayer.playVRStartSound(handler) {
202-
startTimer()
189+
203190
recording = pendingRecording.start(ctx.mainExecutor) { event ->
191+
192+
if (event is VideoRecordEvent.Start) {
193+
onRecordingStart()
194+
}
195+
196+
if (event is VideoRecordEvent.Status) {
197+
updateTimerTime(event.recordingStats.recordedDurationNanos)
198+
}
199+
204200
if (event is VideoRecordEvent.Finalize) {
205201
afterRecordingStops()
206202

@@ -265,9 +261,10 @@ class VideoCapturer(private val mActivity: MainActivity) {
265261
private val dp8 = 8 * mActivity.resources.displayMetrics.density
266262

267263
private fun beforeRecordingStarts() {
268-
269264
mActivity.previewView.keepScreenOn = true
265+
}
270266

267+
private fun onRecordingStart() {
271268
// TODO: Uncomment this once the main indicator UI gets implemented
272269
// mActivity.micOffIcon.visibility = View.GONE
273270

@@ -283,7 +280,6 @@ class VideoCapturer(private val mActivity: MainActivity) {
283280

284281
animator.start()
285282

286-
mActivity.settingsDialog.includeAudioToggle.isEnabled = false
287283
mActivity.settingsDialog.videoQualitySpinner.isEnabled = false
288284
mActivity.settingsDialog.enableEISToggle.isEnabled = false
289285

@@ -321,7 +317,6 @@ class VideoCapturer(private val mActivity: MainActivity) {
321317
mActivity.timerView.visibility = View.GONE
322318
mActivity.flipCamIcon.setImageResource(R.drawable.flip_camera)
323319

324-
mActivity.settingsDialog.includeAudioToggle.isEnabled = true
325320
mActivity.settingsDialog.videoQualitySpinner.isEnabled = true
326321
mActivity.settingsDialog.enableEISToggle.isEnabled = true
327322

@@ -339,7 +334,6 @@ class VideoCapturer(private val mActivity: MainActivity) {
339334
mActivity.cancelButtonView.visibility = View.VISIBLE
340335
mActivity.tabLayout.visibility = View.VISIBLE
341336
}
342-
cancelTimer()
343337

344338
mActivity.previewView.keepScreenOn = false
345339

@@ -352,6 +346,14 @@ class VideoCapturer(private val mActivity: MainActivity) {
352346
mActivity.forceUpdateOrientationSensor()
353347
}
354348

349+
fun muteRecording() {
350+
recording?.mute(true)
351+
}
352+
353+
fun unmuteRecording() {
354+
recording?.mute(false)
355+
}
356+
355357
fun stopRecording() {
356358
recording?.stop()
357359
recording?.close()

0 commit comments

Comments
 (0)