package com.lemenzo.camera.notifier import android.content.Context import android.hardware.Sensor import android.hardware.SensorEvent import android.hardware.SensorEventListener import android.hardware.SensorManager import android.view.View import com.lemenzo.camera.ui.activities.MainActivity import java.lang.ref.WeakReference import kotlin.math.abs import kotlin.math.atan import kotlin.math.floor import kotlin.math.sqrt class SensorOrientationChangeNotifier private constructor( private val mainActivity: MainActivity ) { companion object { private var mInstance: SensorOrientationChangeNotifier? = null fun getInstance(mActivity: MainActivity): SensorOrientationChangeNotifier? { if (mInstance == null) mInstance = SensorOrientationChangeNotifier(mActivity) return mInstance } fun clearInstance() { mInstance = null } // Greater the threshold (in degrees), more the chances of the gyroscope being // visible via the ENTRY_CRITERIA private const val X_THRESHOLD = 5 // The gyroscope shall be explicitly made visible only if it's within the ENTRY_ // CRITERIA and if the device isn't moving too fast i.e. (lastX - currentX) is below // threshold private const val X_ENTRY_MIN = -8F private const val X_ENTRY_MAX = 8F // If the current angle for a given axis is beyond the EXIT_CRITERIA the listener // will just hide the gyroscope (and just return the control back from the method as // executing further statements won't make sense) private const val X_EXIT_MIN = -45F private const val X_EXIT_MAX = 45F private const val Z_THRESHOLD = 5 private const val Z_ENTRY_MIN = -25F private const val Z_ENTRY_MAX = 25F private const val Z_EXIT_MIN = -45F private const val Z_EXIT_MAX = 45F } var mOrientation = mainActivity.getRotation() private set private val mListeners = ArrayList>(3) private val mSensorEventListener: NotifierSensorEventListener private val mSensorManager: SensorManager /** * Call on activity reset() */ private fun onResume() { mSensorManager.registerListener( mSensorEventListener, mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL ) notifyListeners(true) } /** * Call on activity onPause() */ private fun onPause() { mSensorManager.unregisterListener(mSensorEventListener) } inner class NotifierSensorEventListener : SensorEventListener { var lastX = 0f var lastZ = 0f private val ALPHA = 0.7f private val filteredValues = FloatArray(3) override fun onSensorChanged(event: SensorEvent) { filteredValues[0] = ALPHA * filteredValues[0] + (1 - ALPHA) * event.values[0] filteredValues[1] = ALPHA * filteredValues[1] + (1 - ALPHA) * event.values[1] filteredValues[2] = ALPHA * filteredValues[2] + (1 - ALPHA) * event.values[2] var x : Float = filteredValues[0] var y : Float = filteredValues[1] var z : Float = filteredValues[2] var newOrientation = mOrientation if (x < 5 && x > -5 && y > 5) newOrientation = 0 else if (x < -5 && y < 5 && y > -5) newOrientation = 90 else if (x < 5 && x > -5 && y < -5) newOrientation = 180 else if (x > 5 && y < 5 && y > -5) newOrientation = 270 if (mOrientation != newOrientation) { mOrientation = newOrientation notifyListeners() } if (!mainActivity.camConfig.shouldShowGyroscope()) { mainActivity.gCircleFrame.visibility = View.GONE return } if (newOrientation == 90 || newOrientation == 270) { val t = x x = y y = t } x = ((180 / Math.PI) * atan(x / sqrt(y * y + z * z))).toFloat() z = ((180 / Math.PI) * atan(z / sqrt(y * y + x * x))).toFloat() if (z < Z_EXIT_MIN || z > Z_EXIT_MAX) { mainActivity.gCircleFrame.visibility = View.GONE return } if (x < X_EXIT_MIN || x > X_EXIT_MAX) { mainActivity.gCircleFrame.visibility = View.GONE return } if (x in X_ENTRY_MIN..X_ENTRY_MAX) { if (abs(x - lastX) < X_THRESHOLD) { if (z in Z_ENTRY_MIN..Z_ENTRY_MAX) { if (abs(z - lastZ) < Z_THRESHOLD) { mainActivity.gCircleFrame.visibility = View.VISIBLE } } } } updateGyro(x, z) } fun updateGyro(xAngle: Float, zAngle: Float) { val xAngle = floor(xAngle) val zAngle = floor(zAngle) mainActivity.onDeviceAngleChange(xAngle, zAngle) lastX = xAngle lastZ = zAngle } override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {} } fun forceUpdateGyro() { mSensorEventListener.let { it.updateGyro(it.lastX, it.lastZ) } } interface Listener { fun onOrientationChange(orientation: Int) } fun addListener(listener: Listener) { if (get(listener) == null) // prevent duplications mListeners.add(WeakReference(listener)) if (mListeners.size == 1) { onResume() // this is the first client } } fun remove(listener: Listener) { val listenerWR = get(listener) remove(listenerWR) } private fun remove(listenerWR: WeakReference?) { if (listenerWR != null) mListeners.remove(listenerWR) if (mListeners.size == 0) { onPause() } } private operator fun get(listener: Listener): WeakReference? { for (existingListener in mListeners) if (existingListener.get() === listener) return existingListener return null } fun notifyListeners(manualUpdate: Boolean = false) { if (manualUpdate) { mOrientation = mainActivity.getRotation() } val deadLinksArr = ArrayList>() for (wr in mListeners) { if (wr.get() == null) deadLinksArr.add(wr) else wr.get()!! .onOrientationChange(mOrientation) } // remove dead references for (wr in deadLinksArr) { mListeners.remove(wr) } } init { mSensorEventListener = NotifierSensorEventListener() mSensorManager = mainActivity.getSystemService(Context.SENSOR_SERVICE) as SensorManager } }