Skip to content

Commit

Permalink
Implemented a microphone access service to handle background micropho…
Browse files Browse the repository at this point in the history
…ne usage in calls.
  • Loading branch information
AmitShilo committed Sep 20, 2024
1 parent 04a49e9 commit fc05d99
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 0 deletions.
2 changes: 2 additions & 0 deletions library/ui-strings/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,8 @@

<string name="call_remove_jitsi_widget_progress">Ending call…</string>

<string name="microphone_in_use_title">Microphone in use</string>

<!-- permissions Android M -->
<string name="permissions_rationale_popup_title">Information</string>
<!-- Note to translators: the translation MUST contain the string "${app_name}", which will be replaced by the application name -->
Expand Down
7 changes: 7 additions & 0 deletions vector/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,13 @@
android:foregroundServiceType="mediaProjection"
tools:targetApi="Q" />

<service
android:name=".features.call.audio.MicrophoneAccessService"
android:exported="false"
android:foregroundServiceType="microphone"
android:permission="android.permission.FOREGROUND_SERVICE_MICROPHONE">
</service>

<!-- Receivers -->

<receiver
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import im.vector.app.core.extensions.singletonEntryPoint
import im.vector.app.core.extensions.startForegroundCompat
import im.vector.app.features.call.CallArgs
import im.vector.app.features.call.VectorCallActivity
import im.vector.app.features.call.audio.MicrophoneAccessService
import im.vector.app.features.call.telecom.CallConnection
import im.vector.app.features.call.webrtc.WebRtcCall
import im.vector.app.features.call.webrtc.WebRtcCallManager
Expand Down Expand Up @@ -208,6 +209,9 @@ class CallAndroidService : VectorAndroidService() {
stopForegroundCompat()
mediaSession?.isActive = false
myStopSelf()

// Also stop the microphone service if it is running
stopService(Intent(this, MicrophoneAccessService::class.java))
}
val wasConnected = connectedCallIds.remove(callId)
if (!wasConnected && !terminatedCall.isOutgoing && !rejected && endCallReason != EndCallReason.ANSWERED_ELSEWHERE) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import android.app.PictureInPictureParams
import android.content.Context
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP
import android.content.pm.PackageManager
import android.graphics.Color
import android.media.projection.MediaProjection
import android.media.projection.MediaProjectionManager
Expand Down Expand Up @@ -57,6 +58,7 @@ import im.vector.app.core.utils.PERMISSIONS_FOR_VIDEO_IP_CALL
import im.vector.app.core.utils.checkPermissions
import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.databinding.ActivityCallBinding
import im.vector.app.features.call.audio.MicrophoneAccessService
import im.vector.app.features.call.dialpad.CallDialPadBottomSheet
import im.vector.app.features.call.dialpad.DialPadFragment
import im.vector.app.features.call.transfer.CallTransferActivity
Expand Down Expand Up @@ -245,6 +247,33 @@ class VectorCallActivity :
}
}

private fun startMicrophoneService() {
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(this, android.Manifest.permission.FOREGROUND_SERVICE_MICROPHONE) == PackageManager.PERMISSION_GRANTED) {
Timber.tag(loggerTag.value).d("Starting MicrophoneAccessService.")
val intent = Intent(this, MicrophoneAccessService::class.java)
ContextCompat.startForegroundService(this, intent)
} else {
Timber.tag(loggerTag.value).w("Permissions not granted. Cannot start MicrophoneAccessService.")
}
}

private fun stopMicrophoneService() {
Timber.tag(loggerTag.value).d("Stopping MicrophoneAccessService (if needed).")
val intent = Intent(this, MicrophoneAccessService::class.java)
stopService(intent)
}

override fun onPause() {
super.onPause()
startMicrophoneService()
}

override fun onResume() {
super.onResume()
stopMicrophoneService()
}

override fun onDestroy() {
detachRenderersIfNeeded()
turnScreenOffAndKeyguardOn()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright (c) 2024 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.audio

import android.content.Intent
import android.os.Binder
import android.os.IBinder
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.core.extensions.startForegroundCompat
import im.vector.app.core.services.VectorAndroidService
import im.vector.app.features.notifications.NotificationUtils
import javax.inject.Inject

@AndroidEntryPoint
class MicrophoneAccessService : VectorAndroidService() {

@Inject lateinit var notificationUtils: NotificationUtils
private val binder = LocalBinder()

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
showMicrophoneAccessNotification()

return START_STICKY
}

private fun showMicrophoneAccessNotification() {
val notificationId = System.currentTimeMillis().toInt()
val notification = notificationUtils.buildMicrophoneAccessNotification()
startForegroundCompat(notificationId, notification)
}

override fun onBind(intent: Intent?): IBinder {
return binder
}

inner class LocalBinder : Binder() {
fun getService(): MicrophoneAccessService = this@MicrophoneAccessService
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,19 @@ class NotificationUtils @Inject constructor(
.build()
}

/**
* Creates a notification indicating that the microphone is currently being accessed by the application.
*/
fun buildMicrophoneAccessNotification(): Notification {
return NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID)
.setContentTitle(stringProvider.getString(CommonStrings.microphone_in_use_title))
.setSmallIcon(R.drawable.ic_call_answer)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setColor(ThemeUtils.getColor(context, android.R.attr.colorPrimary))
.setCategory(NotificationCompat.CATEGORY_CALL)
.build()
}

/**
* Creates a notification that indicates the application is initializing.
*/
Expand Down

0 comments on commit fc05d99

Please sign in to comment.