feat: more commands handled, adding lock manager and bluetooth permissions

This commit is contained in:
jubb 2021-11-05 14:36:25 +11:00
parent de4d8e9be4
commit f069d35b14
9 changed files with 545 additions and 57 deletions

View File

@ -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" />

View File

@ -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

View File

@ -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()

View File

@ -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)
}
}
}

View File

@ -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()
}
}

View File

@ -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) {

View File

@ -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;
}
}
};
}

View File

@ -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);
}
}

View File

@ -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());
}
}