diff --git a/CHANGES.md b/CHANGES.md index db98189499..09e1775ae2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ Improvements 🙌: - Add long click gesture to copy userId, user display name, room name, room topic and room alias (#1774) - Fix several issues when uploading bug files (#1889) - Do not propose to verify session if there is only one session and 4S is not configured (#1901) + - Call screen does not use proximity sensor (#1735) Bugfix 🐛: - Display name not shown under Settings/General (#1926) diff --git a/vector/src/main/java/im/vector/app/core/services/VectorService.kt b/vector/src/main/java/im/vector/app/core/services/VectorService.kt index 223d720d8a..888f7a8cac 100644 --- a/vector/src/main/java/im/vector/app/core/services/VectorService.kt +++ b/vector/src/main/java/im/vector/app/core/services/VectorService.kt @@ -17,10 +17,8 @@ package im.vector.app.core.services import android.app.Service -import android.content.Context import android.content.Intent import android.os.IBinder -import im.vector.app.core.extensions.vectorComponent import timber.log.Timber /** @@ -33,10 +31,6 @@ abstract class VectorService : Service() { */ private var mIsSelfDestroyed = false - override fun attachBaseContext(base: Context) { - super.attachBaseContext(vectorComponent().vectorConfiguration().getLocalisedContext(base)) - } - override fun onCreate() { super.onCreate() diff --git a/vector/src/main/java/im/vector/app/features/call/CallProximityManager.kt b/vector/src/main/java/im/vector/app/features/call/CallProximityManager.kt new file mode 100644 index 0000000000..74e6c40783 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/call/CallProximityManager.kt @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * 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 im.vector.app.features.call + +import android.content.Context +import android.hardware.Sensor +import android.hardware.SensorEvent +import android.hardware.SensorEventListener +import android.hardware.SensorManager +import android.os.PowerManager +import androidx.core.content.getSystemService +import im.vector.app.R +import im.vector.app.core.resources.StringProvider +import javax.inject.Inject + +/** + * Manages the proximity sensor and turns the screen off when the proximity sensor activates. + */ +class CallProximityManager @Inject constructor( + context: Context, + private val stringProvider: StringProvider +) : SensorEventListener { + + companion object { + private const val PROXIMITY_WAKE_LOCK_TAG = "PROXIMITY_WAKE_LOCK_TAG" + + // 1 hour + private const val WAKE_LOCK_TIMEOUT_MILLIS = 3_600_000L + } + + private val powerManager = context.getSystemService()!! + private val sensorManager = context.getSystemService()!! + + private var wakeLock: PowerManager.WakeLock? = null + private val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY) + + private val isSupported = sensor != null && powerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK) + + /** + * Start listening the proximity sensor. [stop] function should be called to release the sensor and the WakeLock. + */ + fun start() { + if (isSupported) { + sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL) + } + } + + /** + * Stop listening proximity sensor changes and release the WakeLock. + */ + fun stop() { + if (isSupported) { + sensorManager.unregisterListener(this) + wakeLock + ?.takeIf { it.isHeld } + ?.release() + } + } + + override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { + // NOOP + } + + override fun onSensorChanged(event: SensorEvent) { + val distanceInCentimeters = event.values[0] + if (distanceInCentimeters < sensor?.maximumRange ?: 20f) { + onProximityNear() + } else { + onProximityFar() + } + } + + /** + * Recommending naming convention for WakeLock tags is "app:tag" + */ + private fun generateWakeLockTag() = "${stringProvider.getString(R.string.app_name)}:$PROXIMITY_WAKE_LOCK_TAG" + + private fun onProximityNear() { + if (wakeLock == null) { + wakeLock = powerManager.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, generateWakeLockTag()) + } + wakeLock?.acquire(WAKE_LOCK_TIMEOUT_MILLIS) + } + + private fun onProximityFar() { + wakeLock + ?.takeIf { it.isHeld } + ?.release() + } +} diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt index 8ad1b1a267..cee67cd818 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt @@ -87,7 +87,8 @@ class VectorCallViewModel @AssistedInject constructor( @Assisted initialState: VectorCallViewState, @Assisted val args: CallArgs, val session: Session, - val webRtcPeerConnectionManager: WebRtcPeerConnectionManager + val webRtcPeerConnectionManager: WebRtcPeerConnectionManager, + val proximityManager: CallProximityManager ) : VectorViewModel(initialState) { var call: MxCall? = null @@ -145,10 +146,17 @@ class VectorCallViewModel @AssistedInject constructor( } override fun onAudioDevicesChange(mgr: WebRtcPeerConnectionManager) { + val currentSoundDevice = mgr.audioManager.getCurrentSoundDevice() + if (currentSoundDevice == CallAudioManager.SoundDevice.PHONE) { + proximityManager.start() + } else { + proximityManager.stop() + } + setState { copy( availableSoundDevices = mgr.audioManager.getAvailableSoundDevices(), - soundDevice = mgr.audioManager.getCurrentSoundDevice() + soundDevice = currentSoundDevice ) } } @@ -164,9 +172,7 @@ class VectorCallViewModel @AssistedInject constructor( } init { - initialState.callId?.let { - webRtcPeerConnectionManager.addCurrentCallListener(currentCallListener) session.callSignalingService().getCallWithId(it)?.let { mxCall -> @@ -175,12 +181,18 @@ class VectorCallViewModel @AssistedInject constructor( val item: MatrixItem? = session.getUser(mxCall.otherUserId)?.toMatrixItem() mxCall.addListener(callStateListener) + + val currentSoundDevice = webRtcPeerConnectionManager.audioManager.getCurrentSoundDevice() + if (currentSoundDevice == CallAudioManager.SoundDevice.PHONE) { + proximityManager.start() + } + setState { copy( isVideoCall = mxCall.isVideoCall, callState = Success(mxCall.state), otherUserMatrixItem = item?.let { Success(it) } ?: Uninitialized, - soundDevice = webRtcPeerConnectionManager.audioManager.getCurrentSoundDevice(), + soundDevice = currentSoundDevice, availableSoundDevices = webRtcPeerConnectionManager.audioManager.getAvailableSoundDevices(), isFrontCamera = webRtcPeerConnectionManager.currentCameraType() == CameraType.FRONT, canSwitchCamera = webRtcPeerConnectionManager.canSwitchCamera(), @@ -201,6 +213,7 @@ class VectorCallViewModel @AssistedInject constructor( // session.callService().removeCallListener(callServiceListener) webRtcPeerConnectionManager.removeCurrentCallListener(currentCallListener) this.call?.removeListener(callStateListener) + proximityManager.stop() super.onCleared() }