Skip to content

Commit 83f6380

Browse files
committed
implement accelerometer related api for Android
1 parent 3fa1f5c commit 83f6380

File tree

3 files changed

+142
-22
lines changed

3 files changed

+142
-22
lines changed

android/src/main/java/io/numbersprotocol/capturelite/plugins/previewcamera/PreviewCamera.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import android.hardware.camera2.CameraManager
99
import android.hardware.camera2.CameraMetadata
1010
import android.util.DisplayMetrics
1111
import android.util.Log
12+
import android.view.Surface
1213
import android.view.View
1314
import android.view.ViewGroup
1415
import android.widget.FrameLayout
@@ -182,6 +183,23 @@ class PreviewCamera(private val bridge: Bridge) {
182183
}
183184
}
184185

186+
fun shouldRebuildCameraUseCases(newCustomOrientation: String) {
187+
if (previewCameraFragment?.recording != null) return;
188+
189+
bridge.activity.runOnUiThread {
190+
var orientation = Surface.ROTATION_0
191+
if (newCustomOrientation == "landscapeRight")
192+
orientation = Surface.ROTATION_90
193+
if (newCustomOrientation == "portraitDown")
194+
orientation = Surface.ROTATION_180
195+
if (newCustomOrientation == "landscapeLeft")
196+
orientation = Surface.ROTATION_270
197+
// previewCameraFragment?.bindCameraUseCases(orientation)
198+
Log.d("Custom Orientation", newCustomOrientation)
199+
previewCameraFragment?.customOrientation = orientation
200+
}
201+
}
202+
185203

186204
companion object {
187205

@@ -265,5 +283,8 @@ class PreviewCamera(private val bridge: Bridge) {
265283
return availableCameras
266284
}
267285
}
286+
}
268287

288+
fun Float.isBetween(a: Double, b: Double): Boolean {
289+
return a <= this && this <= b
269290
}

android/src/main/java/io/numbersprotocol/capturelite/plugins/previewcamera/PreviewCameraFragment.kt

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,14 @@ const val ANIMATION_SLOW_MILLIS = 100L
5353
* create an instance of this fragment.
5454
*/
5555
class PreviewCameraFragment : Fragment() {
56+
var customOrientation = Surface.ROTATION_0
57+
5658
/** Android ViewBinding */
5759
private var _fragmentCameraBinding: FragmentPreviewCameraBinding? = null
5860
private val fragmentCameraBinding get() = _fragmentCameraBinding!!
5961

6062
private var videoCapture: VideoCapture<Recorder>? = null
61-
private var recording: Recording? = null
63+
var recording: Recording? = null
6264

6365
private lateinit var outputDirectory: File
6466
private lateinit var broadcastManager: LocalBroadcastManager
@@ -80,9 +82,9 @@ class PreviewCameraFragment : Fragment() {
8082
var flashMode = ImageCapture.FLASH_MODE_OFF
8183
var flashModeAvailable = true;
8284

83-
private val displayManager by lazy {
84-
requireContext().getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
85-
}
85+
// private val displayManager by lazy {
86+
// requireContext().getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
87+
// }
8688

8789
/** Blocking camera operations are performed using this executor */
8890
private lateinit var cameraExecutor: ExecutorService
@@ -142,7 +144,7 @@ class PreviewCameraFragment : Fragment() {
142144

143145
override fun onCreate(savedInstanceState: Bundle?) {
144146
super.onCreate(savedInstanceState)
145-
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
147+
// activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
146148

147149
arguments?.let {
148150
cameraId = it.getString(ARG_PARAM1)
@@ -178,7 +180,7 @@ class PreviewCameraFragment : Fragment() {
178180
// broadcastManager.registerReceiver(volumeDownReceiver, filter)
179181

180182
// Every time the orientation of device changes, update rotation for use cases
181-
displayManager.registerDisplayListener(displayListener, null)
183+
// displayManager.registerDisplayListener(displayListener, null)
182184

183185
//Initialize WindowManager to retrieve display metrics
184186
windowManager = WindowManager(view.context)
@@ -234,6 +236,14 @@ class PreviewCameraFragment : Fragment() {
234236
}
235237

236238
fun takePhoto(call: PluginCall, notifyListener: CapacitorNotifyListener) {
239+
if (customOrientation == Surface.ROTATION_0 || customOrientation == Surface.ROTATION_180) {
240+
imageCapture?.targetRotation = Surface.ROTATION_0
241+
} else if (customOrientation == Surface.ROTATION_270) {
242+
imageCapture?.targetRotation = Surface.ROTATION_90
243+
} else {
244+
imageCapture?.targetRotation = Surface.ROTATION_270
245+
}
246+
237247
imageCapture?.let { imageCapture ->
238248
// Setup image capture metadata
239249
val metadata = ImageCapture.Metadata().apply {
@@ -333,6 +343,16 @@ class PreviewCameraFragment : Fragment() {
333343

334344
// configure Recorder and Start recording to the mediaStoreOutput.
335345
try {
346+
Log.d("Custom Orientation", "capture with $customOrientation")
347+
348+
if(customOrientation == Surface.ROTATION_0 || customOrientation == Surface.ROTATION_180) {
349+
videoCapture?.targetRotation = Surface.ROTATION_0
350+
} else if (customOrientation == Surface.ROTATION_270) {
351+
videoCapture?.targetRotation = Surface.ROTATION_90
352+
} else {
353+
videoCapture?.targetRotation = Surface.ROTATION_270
354+
}
355+
336356
recording = videoCapture!!.output
337357
.prepareRecording(requireActivity(), fileOutputOptions)
338358
.apply {
@@ -477,17 +497,25 @@ class PreviewCameraFragment : Fragment() {
477497
}
478498

479499
/** Declare and bind preview, capture and analysis use cases */
480-
private fun bindCameraUseCases() {
500+
fun bindCameraUseCases(customScreenOrientation: Int = Surface.ROTATION_0) {
481501

482502
// Get screen metrics used to setup camera for full screen resolution
483503
val metrics = windowManager.getCurrentWindowMetrics().bounds
484504

485505
Log.d(TAG, "Screen metrics: ${metrics.width()} x ${metrics.height()}")
486506

487-
val screenAspectRatio = aspectRatio(metrics.width(), metrics.height())
507+
var screenWidth = min(metrics.width(), metrics.height())
508+
var screenHeight = max(metrics.width(), metrics.height())
509+
// if (customScreenOrientation == Surface.ROTATION_90 || customScreenOrientation == Surface.ROTATION_270) {
510+
// screenWidth = metrics.height()
511+
// screenHeight = metrics.width()
512+
// }
513+
514+
var screenAspectRatio = aspectRatio(screenWidth, screenHeight)
488515
Log.d(TAG, "Preview aspect ratio: $screenAspectRatio")
489516

490-
val rotation = fragmentCameraBinding.viewFinder.display.rotation
517+
// val rotation = fragmentCameraBinding.viewFinder.display.rotation
518+
val rotation = customScreenOrientation
491519

492520
// CameraProvider TODO: handle IllegalStateException for cameraProvider
493521
val cameraProvider = cameraProvider
@@ -496,20 +524,20 @@ class PreviewCameraFragment : Fragment() {
496524
// CameraSelector
497525
val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
498526

499-
var aspectRatio = aspectRatio(metrics.width(), metrics.height())
527+
// var aspectRatio = aspectRatio(screenWidth, screenHeight)
500528
if (this.captureQuality == "low") {
501-
aspectRatio = aspectRatio(1920, 1080)
502-
if (rotation % 2 == 0) {
503-
aspectRatio = aspectRatio(1080, 1920)
504-
}
529+
screenAspectRatio = aspectRatio(1080, 1920)
530+
// if (rotation % 2 == 0) {
531+
// screenAspectRatio = aspectRatio(1920, 1080)
532+
// }
505533
}
506534
// Preview
507535
preview = Preview.Builder()
508536
// We request aspect ratio but no resolution
509-
.setTargetAspectRatio(aspectRatio)
537+
.setTargetAspectRatio(screenAspectRatio)
510538
// .setTargetAspectRatio(aspectRatio)
511539
// Set initial target rotation
512-
.setTargetRotation(rotation)
540+
.setTargetRotation(Surface.ROTATION_0)
513541
.build()
514542
.also { it.setSurfaceProvider(fragmentCameraBinding.viewFinder.surfaceProvider) }
515543

@@ -526,10 +554,10 @@ class PreviewCameraFragment : Fragment() {
526554
// .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
527555
// We request aspect ratio but no resolution to match preview config, but letting
528556
// CameraX optimize for whatever specific resolution best fits our use cases
529-
.setTargetAspectRatio(aspectRatio)
557+
.setTargetAspectRatio(screenAspectRatio)
530558
// Set initial target rotation, we will have to call this again if rotation changes
531559
// during the lifecycle of this use case
532-
.setTargetRotation(rotation)
560+
.setTargetRotation(Surface.ROTATION_0)
533561
// .setCaptureMode(captureMode)
534562
.setJpegQuality(imageQuality)
535563
.setFlashMode(flashMode)
@@ -544,6 +572,8 @@ class PreviewCameraFragment : Fragment() {
544572
).build()
545573
videoCapture = VideoCapture.withOutput(recorder)
546574

575+
videoCapture!!.targetRotation = Surface.ROTATION_0
576+
547577
try {
548578
// Must unbind the use-cases before rebinding them
549579
cameraProvider.unbindAll()
@@ -743,7 +773,7 @@ class PreviewCameraFragment : Fragment() {
743773

744774
override fun onDestroy() {
745775

746-
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
776+
// activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
747777

748778
_fragmentCameraBinding = null
749779
super.onDestroy()
@@ -752,7 +782,7 @@ class PreviewCameraFragment : Fragment() {
752782

753783
// Unregister the broadcast receivers and listeners
754784
// broadcastManager.unregisterReceiver(volumeDownReceiver)
755-
displayManager.unregisterDisplayListener(displayListener)
785+
// displayManager.unregisterDisplayListener(displayListener)
756786
}
757787

758788
override fun onDestroyView() {

android/src/main/java/io/numbersprotocol/capturelite/plugins/previewcamera/PreviewCameraPlugin.kt

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
package io.numbersprotocol.capturelite.plugins.previewcamera
22

33
import android.Manifest
4+
import android.content.Context
5+
import android.hardware.Sensor
6+
import android.hardware.SensorEvent
7+
import android.hardware.SensorEventListener
8+
import android.hardware.SensorManager
9+
import android.util.Log
410
import com.getcapacitor.*
511
import com.getcapacitor.annotation.CapacitorPlugin
612
import com.getcapacitor.annotation.Permission
713
import com.getcapacitor.annotation.PermissionCallback
14+
import kotlin.math.abs
15+
import kotlin.math.atan2
816

917

1018
@CapacitorPlugin(
@@ -20,7 +28,7 @@ import com.getcapacitor.annotation.PermissionCallback
2028
]
2129

2230
)
23-
class PreviewCameraPlugin : Plugin() {
31+
class PreviewCameraPlugin : Plugin(), SensorEventListener {
2432

2533
companion object {
2634
const val CAMERA_PERMISSION_ALIAS = "cameraPermissionAlias"
@@ -31,8 +39,16 @@ class PreviewCameraPlugin : Plugin() {
3139

3240
private var allowUsersRecordVideoWithoutSound = false
3341

42+
private lateinit var sensorManager: SensorManager
43+
44+
private var lastSensorNotificationTimeInMilliseconds = System.currentTimeMillis()
45+
private val sensorNotificationIntervalInMilliseconds = 1400
46+
47+
private var customOrientation = ".portraitUp"
48+
3449
override fun load() {
3550
implementation = PreviewCamera(bridge)
51+
sensorManager = bridge.activity.getSystemService(Context.SENSOR_SERVICE) as SensorManager
3652
super.load()
3753
}
3854

@@ -76,16 +92,27 @@ class PreviewCameraPlugin : Plugin() {
7692

7793

7894
if (hasCameraPermission) {
95+
startAccelerometerBroadcast()
7996
implementation.startPreview(call)
8097
} else {
8198
requestPermissionForAlias(CAMERA_PERMISSION_ALIAS, call, "handleCameraPermissionResult")
8299
// requestPermissionForAlias(RECORD_AUDIO_PERMISSION_ALIAS, call, "handleRecordAudioPermissionResult")
83100
}
84101
}
85102

103+
private fun startAccelerometerBroadcast() {
104+
val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
105+
sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI)
106+
}
107+
108+
private fun stopAccelerometerBroadcast() {
109+
sensorManager.unregisterListener(this)
110+
}
111+
86112
@PluginMethod
87113
fun stopPreview(call: PluginCall) {
88114
implementation.stopPreview(call);
115+
stopAccelerometerBroadcast()
89116
}
90117

91118
@PluginMethod
@@ -110,7 +137,7 @@ class PreviewCameraPlugin : Plugin() {
110137
}
111138

112139
@PluginMethod
113-
fun setQuality(call: PluginCall){
140+
fun setQuality(call: PluginCall) {
114141
val quality = call.getString("quality") ?: "hq"
115142
implementation.setQuality(quality)
116143
call.resolve()
@@ -165,6 +192,7 @@ class PreviewCameraPlugin : Plugin() {
165192
@PermissionCallback
166193
private fun handleCameraPermissionResult(call: PluginCall) {
167194
if (PermissionState.GRANTED == getPermissionState(CAMERA_PERMISSION_ALIAS)) {
195+
startAccelerometerBroadcast()
168196
implementation.startPreview(call)
169197
} else {
170198
call.reject("Camera permission not granted")
@@ -185,4 +213,45 @@ class PreviewCameraPlugin : Plugin() {
185213
}
186214
}
187215
}
216+
217+
override fun onSensorChanged(sensorEvent: SensorEvent?) {
218+
if (!shouldNotifyAccelerometerChange()) return
219+
if (sensorEvent?.sensor?.type == Sensor.TYPE_ACCELEROMETER) {
220+
val (accelerationX, accelerationY, accelerationZ) = sensorEvent.values
221+
val preciseInclinationInRad = atan2(accelerationY, accelerationX)
222+
val inclinationRad = String.format("%.1f", preciseInclinationInRad).toFloat()
223+
224+
var newCustomOrientation = "portraitUp"
225+
if (inclinationRad.isBetween(0.6, 2.2))
226+
newCustomOrientation = "portraitUp"
227+
if (inclinationRad.isBetween(2.3, 3.3) || inclinationRad.isBetween(-3.3, -2.3))
228+
newCustomOrientation = "landscapeRight"
229+
if (inclinationRad.isBetween(-2.3, -0.9))
230+
newCustomOrientation = "portraitDown"
231+
if (inclinationRad.isBetween(-0.9, 0.0) || inclinationRad.isBetween(0.0, 0.6))
232+
newCustomOrientation = "landscapeLeft"
233+
234+
235+
if (newCustomOrientation != customOrientation) {
236+
implementation.shouldRebuildCameraUseCases(newCustomOrientation)
237+
}
238+
239+
val data = JSObject()
240+
data.put("orientation", newCustomOrientation)
241+
notifyListeners("accelerometerOrientation", data)
242+
243+
lastSensorNotificationTimeInMilliseconds = System.currentTimeMillis()
244+
customOrientation = newCustomOrientation
245+
}
246+
}
247+
248+
override fun onAccuracyChanged(p0: Sensor?, p1: Int) {
249+
// No logic needed here overriding onSensorChanged is enough for our use case
250+
}
251+
252+
private fun shouldNotifyAccelerometerChange(): Boolean {
253+
val currentTime = System.currentTimeMillis()
254+
val difference = currentTime - lastSensorNotificationTimeInMilliseconds
255+
return difference > sensorNotificationIntervalInMilliseconds;
256+
}
188257
}

0 commit comments

Comments
 (0)