feat: more commands handled, adding lock manager and bluetooth permissions
This commit is contained in:
parent
de4d8e9be4
commit
f069d35b14
|
@ -31,6 +31,7 @@
|
|||
android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
<uses-permission android:name="network.loki.messenger.ACCESS_SESSION_SECRETS" />
|
||||
|
|
|
@ -19,7 +19,6 @@ import org.session.libsession.utilities.Util
|
|||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.thoughtcrime.securesms.calls.WebRtcCallActivity
|
||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||
import org.thoughtcrime.securesms.util.CallNotificationBuilder
|
||||
import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_ESTABLISHED
|
||||
import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_INCOMING_CONNECTING
|
||||
|
@ -28,6 +27,8 @@ import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_OU
|
|||
import org.thoughtcrime.securesms.webrtc.*
|
||||
import org.thoughtcrime.securesms.webrtc.CallManager.CallState.*
|
||||
import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger
|
||||
import org.thoughtcrime.securesms.webrtc.locks.LockManager
|
||||
import org.webrtc.SessionDescription
|
||||
import java.lang.AssertionError
|
||||
import java.util.*
|
||||
import java.util.concurrent.ExecutionException
|
||||
|
@ -52,6 +53,7 @@ class WebRtcCallService: Service() {
|
|||
const val ACTION_SET_MUTE_AUDIO = "SET_MUTE_AUDIO"
|
||||
const val ACTION_SET_MUTE_VIDEO = "SET_MUTE_VIDEO"
|
||||
const val ACTION_FLIP_CAMERA = "FLIP_CAMERA"
|
||||
const val ACTION_BLUETOOTH_CHANGE = "BLUETOOTH_CHANGE"
|
||||
const val ACTION_UPDATE_AUDIO = "UPDATE_AUDIO"
|
||||
const val ACTION_WIRED_HEADSET_CHANGE = "WIRED_HEADSET_CHANGE"
|
||||
const val ACTION_SCREEN_OFF = "SCREEN_OFF"
|
||||
|
@ -107,6 +109,7 @@ class WebRtcCallService: Service() {
|
|||
private var lastNotificationId: Int = INVALID_NOTIFICATION_ID
|
||||
private var lastNotification: Notification? = null
|
||||
|
||||
private val lockManager by lazy { LockManager(this) }
|
||||
private val serviceExecutor = Executors.newSingleThreadExecutor()
|
||||
private val timeoutExecutor = Executors.newScheduledThreadPool(1)
|
||||
private val hangupOnCallAnswered = HangUpRtcOnPstnCallAnsweredListener {
|
||||
|
@ -145,9 +148,9 @@ class WebRtcCallService: Service() {
|
|||
action == ACTION_REMOTE_HANGUP -> handleRemoteHangup(intent)
|
||||
action == ACTION_SET_MUTE_AUDIO -> handleSetMuteAudio(intent)
|
||||
action == ACTION_SET_MUTE_VIDEO -> handleSetMuteVideo(intent)
|
||||
action == ACTION_FLIP_CAMERA -> handlesetCameraFlip(intent)
|
||||
// action == ACTION_BLUETOOTH_CHANGE -> handleBluetoothChange(intent)
|
||||
// action == ACTION_WIRED_HEADSET_CHANGE -> handleWiredHeadsetChange(intent)
|
||||
action == ACTION_FLIP_CAMERA -> handleSetCameraFlip(intent)
|
||||
action == ACTION_BLUETOOTH_CHANGE -> handleBluetoothChange(intent)
|
||||
action == ACTION_WIRED_HEADSET_CHANGE -> handleWiredHeadsetChanged(intent)
|
||||
action == ACTION_SCREEN_OFF -> handleScreenOffChange(intent)
|
||||
action == ACTION_REMOTE_VIDEO_MUTE -> handleRemoteVideoMute(intent)
|
||||
action == ACTION_RESPONSE_MESSAGE -> handleResponseMessage(intent)
|
||||
|
@ -369,9 +372,64 @@ class WebRtcCallService: Service() {
|
|||
|
||||
private fun handleSetMuteVideo(intent: Intent) {
|
||||
val muted = intent.getBooleanExtra(EXTRA_MUTE, false)
|
||||
callManager.handleSetMuteVideo(muted)
|
||||
callManager.handleSetMuteVideo(muted, lockManager)
|
||||
}
|
||||
|
||||
private fun handleSetCameraFlip(intent: Intent) {
|
||||
callManager.handleSetCameraFlip()
|
||||
}
|
||||
|
||||
private fun handleBluetoothChange(intent: Intent) {
|
||||
val bluetoothAvailable = intent.getBooleanExtra(EXTRA_AVAILABLE, false)
|
||||
callManager.postBluetoothAvailable(bluetoothAvailable)
|
||||
}
|
||||
|
||||
private fun handleWiredHeadsetChanged(intent: Intent) {
|
||||
callManager.handleWiredHeadsetChanged(intent.getBooleanExtra(EXTRA_AVAILABLE, false))
|
||||
}
|
||||
|
||||
private fun handleScreenOffChange(intent: Intent) {
|
||||
callManager.handleScreenOffChange()
|
||||
}
|
||||
|
||||
private fun handleRemoteVideoMute(intent: Intent) {
|
||||
val muted = intent.getBooleanExtra(EXTRA_MUTE, false)
|
||||
val callId = intent.getSerializableExtra(EXTRA_CALL_ID) as UUID
|
||||
|
||||
callManager.handleRemoteVideoMute(muted, callId)
|
||||
}
|
||||
|
||||
|
||||
private fun handleResponseMessage(intent: Intent) {
|
||||
try {
|
||||
val recipient = getRemoteRecipient(intent)
|
||||
val callId = getCallId(intent)
|
||||
val description = intent.getStringExtra(EXTRA_REMOTE_DESCRIPTION)
|
||||
callManager.handleResponseMessage(recipient, callId, SessionDescription(SessionDescription.Type.ANSWER, description))
|
||||
} catch (e: PeerConnectionException) {
|
||||
terminate()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleRemoteIceCandidate(intent: Intent) {
|
||||
|
||||
}
|
||||
|
||||
private fun handleLocalIceCandidate(intent: Intent) {
|
||||
|
||||
}
|
||||
|
||||
private fun handleCallConnected(intent: Intent) {
|
||||
|
||||
}
|
||||
|
||||
private fun handleIsInCallQuery(intent: Intent) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private fun handleCheckTimeout(intent: Intent) {
|
||||
val callId = callManager.callId ?: return
|
||||
val callState = callManager.currentConnectionState
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.webrtc
|
|||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger
|
||||
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager
|
||||
|
||||
@Parcelize
|
||||
|
@ -10,7 +11,7 @@ open class AudioManagerCommand: Parcelable {
|
|||
object Initialize: AudioManagerCommand()
|
||||
|
||||
@Parcelize
|
||||
object StartOutgoingRinger: AudioManagerCommand()
|
||||
data class StartOutgoingRinger(val type: OutgoingRinger.Type): AudioManagerCommand()
|
||||
|
||||
@Parcelize
|
||||
object SilenceIncomingRinger: AudioManagerCommand()
|
||||
|
|
|
@ -5,6 +5,8 @@ import android.content.Intent
|
|||
import android.telephony.TelephonyManager
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.put
|
||||
import nl.komponents.kovenant.Promise
|
||||
import org.session.libsession.messaging.messages.control.CallMessage
|
||||
import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||
|
@ -15,10 +17,13 @@ import org.session.libsignal.utilities.Log
|
|||
import org.thoughtcrime.securesms.webrtc.audio.AudioManagerCompat
|
||||
import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger
|
||||
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager
|
||||
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager.AudioDevice
|
||||
import org.thoughtcrime.securesms.webrtc.locks.LockManager
|
||||
import org.thoughtcrime.securesms.webrtc.video.CameraEventListener
|
||||
import org.thoughtcrime.securesms.webrtc.video.CameraState
|
||||
import org.webrtc.*
|
||||
import java.lang.NullPointerException
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.*
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
|
@ -37,6 +42,10 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
|
|||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val VIDEO_DISABLED_JSON by lazy { buildJsonObject { put("video", false) } }
|
||||
val VIDEO_ENABLED_JSON by lazy { buildJsonObject { put("video", true) } }
|
||||
|
||||
private val TAG = Log.tag(CallManager::class.java)
|
||||
val CONNECTED_STATES = arrayOf(CallState.STATE_CONNECTED)
|
||||
val PENDING_CONNECTION_STATES = arrayOf(
|
||||
|
@ -50,11 +59,9 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
|
|||
CallState.STATE_REMOTE_RINGING,
|
||||
CallState.STATE_CONNECTED
|
||||
)
|
||||
val DISCONNECTED_STATES = arrayOf(CallState.STATE_IDLE)
|
||||
private const val DATA_CHANNEL_NAME = "signaling"
|
||||
}
|
||||
|
||||
|
||||
private val signalAudioManager: SignalAudioManager = SignalAudioManager(context, this, audioManager)
|
||||
|
||||
private val _audioEvents = MutableStateFlow(StateEvent.AudioEnabled(false))
|
||||
|
@ -97,11 +104,11 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
|
|||
}
|
||||
|
||||
fun initializeAudioForCall() {
|
||||
signalAudioManager.initializeAudioForCall()
|
||||
signalAudioManager.handleCommand(AudioManagerCommand.Initialize)
|
||||
}
|
||||
|
||||
fun startOutgoingRinger(ringerType: OutgoingRinger.Type) {
|
||||
signalAudioManager.startOutgoingRinger(ringerType)
|
||||
signalAudioManager.handleCommand(AudioManagerCommand.StartOutgoingRinger(ringerType))
|
||||
}
|
||||
|
||||
fun postConnectionEvent(newState: CallState) {
|
||||
|
@ -112,36 +119,6 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
|
|||
_callStateEvents.value = newState
|
||||
}
|
||||
|
||||
private fun createCameraCapturer(enumerator: CameraEnumerator): CameraVideoCapturer? {
|
||||
val deviceNames = enumerator.deviceNames
|
||||
|
||||
// First, try to find front facing camera
|
||||
Log.d("Loki-RTC-vid", "Looking for front facing cameras.")
|
||||
for (deviceName in deviceNames) {
|
||||
if (enumerator.isFrontFacing(deviceName)) {
|
||||
Log.d("Loki-RTC-vid", "Creating front facing camera capturer.")
|
||||
val videoCapturer = enumerator.createCapturer(deviceName, null)
|
||||
if (videoCapturer != null) {
|
||||
return videoCapturer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Front facing camera not found, try something else
|
||||
Log.d("Loki-RTC-vid", "Looking for other cameras.")
|
||||
for (deviceName in deviceNames) {
|
||||
if (!enumerator.isFrontFacing(deviceName)) {
|
||||
Log.d("Loki-RTC-vid", "Creating other camera capturer.")
|
||||
val videoCapturer = enumerator.createCapturer(deviceName, null)
|
||||
if (videoCapturer != null) {
|
||||
return videoCapturer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
override fun newCallMessage(callMessage: SignalServiceProtos.CallMessage) {
|
||||
|
||||
}
|
||||
|
@ -262,7 +239,7 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
|
|||
TODO("interpret the data channel buffer and check for signals")
|
||||
}
|
||||
|
||||
override fun onAudioDeviceChanged(activeDevice: SignalAudioManager.AudioDevice, devices: Set<SignalAudioManager.AudioDevice>) {
|
||||
override fun onAudioDeviceChanged(activeDevice: AudioDevice, devices: Set<AudioDevice>) {
|
||||
signalAudioManager.handleCommand(AudioManagerCommand())
|
||||
}
|
||||
|
||||
|
@ -279,7 +256,7 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
|
|||
}
|
||||
|
||||
fun stop() {
|
||||
signalAudioManager.stop(currentConnectionState in OUTGOING_STATES)
|
||||
signalAudioManager.handleCommand(AudioManagerCommand.Stop(currentConnectionState in OUTGOING_STATES))
|
||||
peerConnection?.dispose()
|
||||
peerConnection = null
|
||||
|
||||
|
@ -295,8 +272,8 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
|
|||
localCameraState = CameraState.UNKNOWN
|
||||
recipient = null
|
||||
callId = null
|
||||
microphoneEnabled = true
|
||||
remoteVideoEnabled = false
|
||||
_audioEvents.value = StateEvent.AudioEnabled(false)
|
||||
_videoEvents.value = StateEvent.VideoEnabled(false)
|
||||
pendingOutgoingIceUpdates.clear()
|
||||
pendingIncomingIceUpdates.clear()
|
||||
}
|
||||
|
@ -410,10 +387,93 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
|
|||
peerConnection?.setAudioEnabled(_audioEvents.value.isEnabled)
|
||||
}
|
||||
|
||||
fun handleSetMuteVideo(muted: Boolean) {
|
||||
fun handleSetMuteVideo(muted: Boolean, lockManager: LockManager) {
|
||||
_videoEvents.value = StateEvent.VideoEnabled(!muted)
|
||||
peerConnection?.setVideoEnabled(_videoEvents.value.isEnabled)
|
||||
TODO()
|
||||
dataChannel?.let { channel ->
|
||||
val toSend = if (muted) VIDEO_DISABLED_JSON else VIDEO_ENABLED_JSON
|
||||
val buffer = DataChannel.Buffer(ByteBuffer.wrap(toSend.toString().encodeToByteArray()), false)
|
||||
channel.send(buffer)
|
||||
}
|
||||
|
||||
if (currentConnectionState == CallState.STATE_CONNECTED) {
|
||||
if (localCameraState.enabled) lockManager.updatePhoneState(LockManager.PhoneState.IN_VIDEO)
|
||||
else lockManager.updatePhoneState(LockManager.PhoneState.IN_CALL)
|
||||
}
|
||||
|
||||
if (localCameraState.enabled
|
||||
&& !signalAudioManager.isSpeakerphoneOn()
|
||||
&& !signalAudioManager.isBluetoothScoOn()
|
||||
&& !signalAudioManager.isWiredHeadsetOn()
|
||||
) {
|
||||
signalAudioManager.handleCommand(AudioManagerCommand.SetUserDevice(AudioDevice.SPEAKER_PHONE))
|
||||
}
|
||||
}
|
||||
|
||||
fun handleSetCameraFlip() {
|
||||
if (!localCameraState.enabled) return
|
||||
peerConnection?.let { connection ->
|
||||
connection.flipCamera()
|
||||
localCameraState = connection.getCameraState()
|
||||
}
|
||||
}
|
||||
|
||||
fun postBluetoothAvailable(available: Boolean) {
|
||||
// TODO: _bluetoothEnabled.value = available
|
||||
}
|
||||
|
||||
fun handleWiredHeadsetChanged(present: Boolean) {
|
||||
if (currentConnectionState in arrayOf(CallState.STATE_CONNECTED,
|
||||
CallState.STATE_DIALING,
|
||||
CallState.STATE_REMOTE_RINGING)) {
|
||||
if (present && signalAudioManager.isSpeakerphoneOn()) {
|
||||
signalAudioManager.handleCommand(AudioManagerCommand.SetUserDevice(AudioDevice.WIRED_HEADSET))
|
||||
} else if (!present && !signalAudioManager.isSpeakerphoneOn() && !signalAudioManager.isBluetoothScoOn() && localCameraState.enabled) {
|
||||
signalAudioManager.handleCommand(AudioManagerCommand.SetUserDevice(AudioDevice.SPEAKER_PHONE))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun handleScreenOffChange() {
|
||||
if (currentConnectionState in arrayOf(CallState.STATE_ANSWERING, CallState.STATE_LOCAL_RINGING)) {
|
||||
signalAudioManager.handleCommand(AudioManagerCommand.SilenceIncomingRinger)
|
||||
}
|
||||
}
|
||||
|
||||
fun handleRemoteVideoMute(muted: Boolean, intentCallId: UUID) {
|
||||
val recipient = recipient ?: return
|
||||
val callId = callId ?: return
|
||||
if (currentConnectionState != CallState.STATE_CONNECTED || callId != intentCallId) {
|
||||
Log.w(TAG,"Got video toggle for inactive call, ignoring..")
|
||||
return
|
||||
}
|
||||
|
||||
_remoteVideoEvents.value = StateEvent.VideoEnabled(!muted)
|
||||
}
|
||||
|
||||
fun handleResponseMessage(recipient: Recipient, callId: UUID, answer: SessionDescription) {
|
||||
if (currentConnectionState != CallState.STATE_DIALING || recipient != this.recipient || callId != this.callId) {
|
||||
Log.w(TAG,"Got answer for recipient and call ID we're not currently dialing")
|
||||
return
|
||||
}
|
||||
|
||||
val connection = peerConnection ?: throw AssertionError("assert")
|
||||
|
||||
connection.setRemoteDescription(answer)
|
||||
}
|
||||
|
||||
fun handleRemoteIceCandidate(iceCandidates: List<IceCandidate>, callId: UUID) {
|
||||
if (callId != this.callId) {
|
||||
Log.w(TAG, "Got remote ice candidates for a call that isn't active")
|
||||
}
|
||||
|
||||
peerConnection?.let { connection ->
|
||||
iceCandidates.forEach { candidate ->
|
||||
connection.addIceCandidate(candidate)
|
||||
}
|
||||
} ?: run {
|
||||
pendingIncomingIceUpdates.addAll(iceCandidates)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -81,7 +81,10 @@ class PeerConnectionWrapper(context: Context,
|
|||
}
|
||||
|
||||
fun createDataChannel(channelName: String): DataChannel {
|
||||
|
||||
val dataChannelConfiguration = DataChannel.Init().apply {
|
||||
ordered = true
|
||||
}
|
||||
return peerConnection.createDataChannel(channelName, dataChannelConfiguration)
|
||||
}
|
||||
|
||||
fun addIceCandidate(candidate: IceCandidate) {
|
||||
|
@ -228,4 +231,8 @@ class PeerConnectionWrapper(context: Context,
|
|||
}
|
||||
}
|
||||
|
||||
fun flipCamera() {
|
||||
camera.flip()
|
||||
}
|
||||
|
||||
}
|
|
@ -75,7 +75,7 @@ class SignalAudioManager(private val context: Context,
|
|||
is AudioManagerCommand.SetUserDevice -> selectAudioDevice(command.device)
|
||||
is AudioManagerCommand.StartIncomingRinger -> startIncomingRinger(command.vibrate)
|
||||
is AudioManagerCommand.SilenceIncomingRinger -> silenceIncomingRinger()
|
||||
is AudioManagerCommand.StartOutgoingRinger -> startOutgoingRinger()
|
||||
is AudioManagerCommand.StartOutgoingRinger -> startOutgoingRinger(command.type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -128,7 +128,7 @@ class SignalAudioManager(private val context: Context,
|
|||
Log.d(TAG, "Started")
|
||||
}
|
||||
|
||||
fun stop(playDisconnect: Boolean) {
|
||||
private fun stop(playDisconnect: Boolean) {
|
||||
Log.d(TAG, "Stopping. state: $state")
|
||||
if (state == State.UNINITIALIZED) {
|
||||
Log.i(TAG, "Trying to stop AudioManager in incorrect state: $state")
|
||||
|
@ -166,7 +166,7 @@ class SignalAudioManager(private val context: Context,
|
|||
Log.d(TAG, "Stopped")
|
||||
}
|
||||
|
||||
fun shutdown() {
|
||||
private fun shutdown() {
|
||||
handler.post {
|
||||
stop(false)
|
||||
if (commandAndControlThread != null) {
|
||||
|
@ -177,7 +177,7 @@ class SignalAudioManager(private val context: Context,
|
|||
}
|
||||
}
|
||||
|
||||
fun updateAudioDeviceState() {
|
||||
private fun updateAudioDeviceState() {
|
||||
handler.assertHandlerThread()
|
||||
|
||||
Log.i(
|
||||
|
@ -342,13 +342,13 @@ class SignalAudioManager(private val context: Context,
|
|||
incomingRinger.stop()
|
||||
}
|
||||
|
||||
fun startOutgoingRinger(type: OutgoingRinger.Type) {
|
||||
private fun startOutgoingRinger(type: OutgoingRinger.Type) {
|
||||
Log.i(TAG, "startOutgoingRinger(): currentDevice: $selectedAudioDevice")
|
||||
|
||||
androidAudioManager.mode = AudioManager.MODE_IN_COMMUNICATION
|
||||
setMicrophoneMute(false)
|
||||
|
||||
outgoingRinger.start(OutgoingRinger.Type.RINGING)
|
||||
outgoingRinger.start(type)
|
||||
}
|
||||
|
||||
private fun onWiredHeadsetChange(pluggedIn: Boolean, hasMic: Boolean) {
|
||||
|
@ -357,10 +357,11 @@ class SignalAudioManager(private val context: Context,
|
|||
updateAudioDeviceState()
|
||||
}
|
||||
|
||||
fun initializeAudioForCall() {
|
||||
val audioManager: AudioManager = ServiceUtil.getAudioManager(context)
|
||||
audioManager.requestAudioFocus(null, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
|
||||
}
|
||||
fun isSpeakerphoneOn(): Boolean = androidAudioManager.isSpeakerphoneOn
|
||||
|
||||
fun isBluetoothScoOn(): Boolean = androidAudioManager.isBluetoothScoOn
|
||||
|
||||
fun isWiredHeadsetOn(): Boolean = androidAudioManager.isWiredHeadsetOn
|
||||
|
||||
private inner class WiredHeadsetReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.webrtc.locks;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
|
||||
import org.session.libsignal.utilities.Log;
|
||||
|
||||
/**
|
||||
* This class is used to listen to the accelerometer to monitor the
|
||||
* orientation of the phone. The client of this class is notified when
|
||||
* the orientation changes between horizontal and vertical.
|
||||
*/
|
||||
public final class AccelerometerListener {
|
||||
private static final String TAG = "AccelerometerListener";
|
||||
private static final boolean DEBUG = true;
|
||||
private static final boolean VDEBUG = false;
|
||||
|
||||
private SensorManager mSensorManager;
|
||||
private Sensor mSensor;
|
||||
|
||||
// mOrientation is the orientation value most recently reported to the client.
|
||||
private int mOrientation;
|
||||
|
||||
// mPendingOrientation is the latest orientation computed based on the sensor value.
|
||||
// This is sent to the client after a rebounce delay, at which point it is copied to
|
||||
// mOrientation.
|
||||
private int mPendingOrientation;
|
||||
|
||||
private OrientationListener mListener;
|
||||
|
||||
// Device orientation
|
||||
public static final int ORIENTATION_UNKNOWN = 0;
|
||||
public static final int ORIENTATION_VERTICAL = 1;
|
||||
public static final int ORIENTATION_HORIZONTAL = 2;
|
||||
|
||||
private static final int ORIENTATION_CHANGED = 1234;
|
||||
|
||||
private static final int VERTICAL_DEBOUNCE = 100;
|
||||
private static final int HORIZONTAL_DEBOUNCE = 500;
|
||||
private static final double VERTICAL_ANGLE = 50.0;
|
||||
|
||||
public interface OrientationListener {
|
||||
public void orientationChanged(int orientation);
|
||||
}
|
||||
|
||||
public AccelerometerListener(Context context, OrientationListener listener) {
|
||||
mListener = listener;
|
||||
mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
|
||||
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
|
||||
}
|
||||
|
||||
public void enable(boolean enable) {
|
||||
if (DEBUG) Log.d(TAG, "enable(" + enable + ")");
|
||||
synchronized (this) {
|
||||
if (enable) {
|
||||
mOrientation = ORIENTATION_UNKNOWN;
|
||||
mPendingOrientation = ORIENTATION_UNKNOWN;
|
||||
mSensorManager.registerListener(mSensorListener, mSensor,
|
||||
SensorManager.SENSOR_DELAY_NORMAL);
|
||||
} else {
|
||||
mSensorManager.unregisterListener(mSensorListener);
|
||||
mHandler.removeMessages(ORIENTATION_CHANGED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setOrientation(int orientation) {
|
||||
synchronized (this) {
|
||||
if (mPendingOrientation == orientation) {
|
||||
// Pending orientation has not changed, so do nothing.
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancel any pending messages.
|
||||
// We will either start a new timer or cancel alltogether
|
||||
// if the orientation has not changed.
|
||||
mHandler.removeMessages(ORIENTATION_CHANGED);
|
||||
|
||||
if (mOrientation != orientation) {
|
||||
// Set timer to send an event if the orientation has changed since its
|
||||
// previously reported value.
|
||||
mPendingOrientation = orientation;
|
||||
Message m = mHandler.obtainMessage(ORIENTATION_CHANGED);
|
||||
// set delay to our debounce timeout
|
||||
int delay = (orientation == ORIENTATION_VERTICAL ? VERTICAL_DEBOUNCE
|
||||
: HORIZONTAL_DEBOUNCE);
|
||||
mHandler.sendMessageDelayed(m, delay);
|
||||
} else {
|
||||
// no message is pending
|
||||
mPendingOrientation = ORIENTATION_UNKNOWN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onSensorEvent(double x, double y, double z) {
|
||||
if (VDEBUG) Log.d(TAG, "onSensorEvent(" + x + ", " + y + ", " + z + ")");
|
||||
|
||||
// If some values are exactly zero, then likely the sensor is not powered up yet.
|
||||
// ignore these events to avoid false horizontal positives.
|
||||
if (x == 0.0 || y == 0.0 || z == 0.0) return;
|
||||
|
||||
// magnitude of the acceleration vector projected onto XY plane
|
||||
double xy = Math.sqrt(x * x + y * y);
|
||||
// compute the vertical angle
|
||||
double angle = Math.atan2(xy, z);
|
||||
// convert to degrees
|
||||
angle = angle * 180.0 / Math.PI;
|
||||
int orientation = (angle > VERTICAL_ANGLE ? ORIENTATION_VERTICAL : ORIENTATION_HORIZONTAL);
|
||||
if (VDEBUG) Log.d(TAG, "angle: " + angle + " orientation: " + orientation);
|
||||
setOrientation(orientation);
|
||||
}
|
||||
|
||||
SensorEventListener mSensorListener = new SensorEventListener() {
|
||||
public void onSensorChanged(SensorEvent event) {
|
||||
onSensorEvent(event.values[0], event.values[1], event.values[2]);
|
||||
}
|
||||
|
||||
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
|
||||
Handler mHandler = new Handler() {
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case ORIENTATION_CHANGED:
|
||||
synchronized (this) {
|
||||
mOrientation = mPendingOrientation;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "orientation: " +
|
||||
(mOrientation == ORIENTATION_HORIZONTAL ? "horizontal"
|
||||
: (mOrientation == ORIENTATION_VERTICAL ? "vertical"
|
||||
: "unknown")));
|
||||
}
|
||||
mListener.orientationChanged(mOrientation);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
package org.thoughtcrime.securesms.webrtc.locks;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.PowerManager;
|
||||
import android.provider.Settings;
|
||||
|
||||
import org.session.libsignal.utilities.Log;
|
||||
|
||||
/**
|
||||
* Maintains wake lock state.
|
||||
*
|
||||
* @author Stuart O. Anderson
|
||||
*/
|
||||
public class LockManager {
|
||||
|
||||
private static final String TAG = LockManager.class.getSimpleName();
|
||||
|
||||
private final PowerManager.WakeLock fullLock;
|
||||
private final PowerManager.WakeLock partialLock;
|
||||
private final WifiManager.WifiLock wifiLock;
|
||||
private final ProximityLock proximityLock;
|
||||
|
||||
private final AccelerometerListener accelerometerListener;
|
||||
private final boolean wifiLockEnforced;
|
||||
|
||||
|
||||
private int orientation = AccelerometerListener.ORIENTATION_UNKNOWN;
|
||||
private boolean proximityDisabled = false;
|
||||
|
||||
public enum PhoneState {
|
||||
IDLE,
|
||||
PROCESSING, //used when the phone is active but before the user should be alerted.
|
||||
INTERACTIVE,
|
||||
IN_CALL,
|
||||
IN_VIDEO
|
||||
}
|
||||
|
||||
private enum LockState {
|
||||
FULL,
|
||||
PARTIAL,
|
||||
SLEEP,
|
||||
PROXIMITY
|
||||
}
|
||||
|
||||
public LockManager(Context context) {
|
||||
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||
fullLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, "signal:full");
|
||||
partialLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "signal:partial");
|
||||
proximityLock = new ProximityLock(pm);
|
||||
|
||||
WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
|
||||
wifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "signal:wifi");
|
||||
|
||||
fullLock.setReferenceCounted(false);
|
||||
partialLock.setReferenceCounted(false);
|
||||
wifiLock.setReferenceCounted(false);
|
||||
|
||||
accelerometerListener = new AccelerometerListener(context, new AccelerometerListener.OrientationListener() {
|
||||
@Override
|
||||
public void orientationChanged(int newOrientation) {
|
||||
orientation = newOrientation;
|
||||
Log.d(TAG, "Orentation Update: " + newOrientation);
|
||||
updateInCallLockState();
|
||||
}
|
||||
});
|
||||
|
||||
wifiLockEnforced = isWifiPowerActiveModeEnabled(context);
|
||||
}
|
||||
|
||||
private boolean isWifiPowerActiveModeEnabled(Context context) {
|
||||
int wifi_pwr_active_mode = Settings.Secure.getInt(context.getContentResolver(), "wifi_pwr_active_mode", -1);
|
||||
Log.d(TAG, "Wifi Activity Policy: " + wifi_pwr_active_mode);
|
||||
|
||||
if (wifi_pwr_active_mode == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateInCallLockState() {
|
||||
if (orientation != AccelerometerListener.ORIENTATION_HORIZONTAL && wifiLockEnforced && !proximityDisabled) {
|
||||
setLockState(LockState.PROXIMITY);
|
||||
} else {
|
||||
setLockState(LockState.FULL);
|
||||
}
|
||||
}
|
||||
|
||||
public void updatePhoneState(PhoneState state) {
|
||||
switch(state) {
|
||||
case IDLE:
|
||||
setLockState(LockState.SLEEP);
|
||||
accelerometerListener.enable(false);
|
||||
break;
|
||||
case PROCESSING:
|
||||
setLockState(LockState.PARTIAL);
|
||||
accelerometerListener.enable(false);
|
||||
break;
|
||||
case INTERACTIVE:
|
||||
setLockState(LockState.FULL);
|
||||
accelerometerListener.enable(false);
|
||||
break;
|
||||
case IN_VIDEO:
|
||||
proximityDisabled = true;
|
||||
accelerometerListener.enable(false);
|
||||
updateInCallLockState();
|
||||
break;
|
||||
case IN_CALL:
|
||||
proximityDisabled = false;
|
||||
accelerometerListener.enable(true);
|
||||
updateInCallLockState();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void setLockState(LockState newState) {
|
||||
switch(newState) {
|
||||
case FULL:
|
||||
fullLock.acquire();
|
||||
partialLock.acquire();
|
||||
wifiLock.acquire();
|
||||
proximityLock.release();
|
||||
break;
|
||||
case PARTIAL:
|
||||
partialLock.acquire();
|
||||
wifiLock.acquire();
|
||||
fullLock.release();
|
||||
proximityLock.release();
|
||||
break;
|
||||
case SLEEP:
|
||||
fullLock.release();
|
||||
partialLock.release();
|
||||
wifiLock.release();
|
||||
proximityLock.release();
|
||||
break;
|
||||
case PROXIMITY:
|
||||
partialLock.acquire();
|
||||
proximityLock.acquire();
|
||||
wifiLock.acquire();
|
||||
fullLock.release();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unhandled Mode: " + newState);
|
||||
}
|
||||
Log.d(TAG, "Entered Lock State: " + newState);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package org.thoughtcrime.securesms.webrtc.locks;
|
||||
|
||||
import android.os.PowerManager;
|
||||
|
||||
import org.session.libsignal.utilities.Log;
|
||||
import org.session.libsignal.utilities.guava.Optional;
|
||||
|
||||
/**
|
||||
* Controls access to the proximity lock.
|
||||
* The proximity lock is not part of the public API.
|
||||
*
|
||||
* @author Stuart O. Anderson
|
||||
*/
|
||||
class ProximityLock {
|
||||
|
||||
private static final String TAG = ProximityLock.class.getSimpleName();
|
||||
|
||||
private final Optional<PowerManager.WakeLock> proximityLock;
|
||||
|
||||
ProximityLock(PowerManager pm) {
|
||||
proximityLock = getProximityLock(pm);
|
||||
}
|
||||
|
||||
private Optional<PowerManager.WakeLock> getProximityLock(PowerManager pm) {
|
||||
if (pm.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
|
||||
return Optional.fromNullable(pm.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, "signal:proximity"));
|
||||
} else {
|
||||
return Optional.absent();
|
||||
}
|
||||
}
|
||||
|
||||
public void acquire() {
|
||||
if (!proximityLock.isPresent() || proximityLock.get().isHeld()) {
|
||||
return;
|
||||
}
|
||||
|
||||
proximityLock.get().acquire();
|
||||
}
|
||||
|
||||
public void release() {
|
||||
if (!proximityLock.isPresent() || !proximityLock.get().isHeld()) {
|
||||
return;
|
||||
}
|
||||
|
||||
proximityLock.get().release(PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY);
|
||||
|
||||
Log.d(TAG, "Released proximity lock:" + proximityLock.get().isHeld());
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue