Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 25 additions & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,13 @@ tauri-plugin-sharekit = { git = "https://github.com/Choochmeque/tauri-plugin-sha

[target.'cfg(target_os = "ios")'.dependencies]
objc2 = "0.6"
objc2-foundation = { version = "0.3", features = ["NSError", "NSFileManager", "NSString", "NSURL"] }
objc2-foundation = { version = "0.3", features = [
"NSError",
"NSFileManager",
"NSString",
"NSURL",
"NSUUID",
] }
objc2-photos = { version = "0.3.2", default-features = false, features = [
"std",
"PHPhotoLibrary",
Expand All @@ -145,6 +151,24 @@ objc2-avf-audio = { version = "0.3", default-features = false, features = [
"AVAudioSession",
"AVAudioSessionTypes",
] }
objc2-call-kit = { version = "0.3", default-features = false, features = [
"std",
"alloc",
"block2",
"dispatch2",
"CXAction",
"CXProvider",
"CXProviderConfiguration",
"CXCallUpdate",
"CXHandle",
"CXCall",
"CXCallAction",
"CXStartCallAction",
"CXEndCallAction",
"CXSetMutedCallAction",
"CXTransaction",
"CXCallController",
] }

[target.'cfg(target_os = "android")'.dependencies]
tauri-plugin-android-fs = { version = "=28.4.0", features = ["legacy-storage-permission"] }
Expand Down
2 changes: 2 additions & 0 deletions src-tauri/gen/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<!-- Keep the CPU awake so the WebView's WebRTC keeps running while backgrounded -->
<uses-permission android:name="android.permission.WAKE_LOCK" />

<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.microphone" android:required="false" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,20 @@ import android.app.NotificationManager
import android.app.Service
import android.content.pm.PackageManager
import android.content.pm.ServiceInfo
import android.media.AudioAttributes
import android.media.AudioFocusRequest
import android.media.AudioManager
import android.os.Build
import android.os.IBinder
import android.os.PowerManager
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.ServiceCompat

class CallForegroundService : Service() {
private var wakeLock: PowerManager.WakeLock? = null
private var audioFocusRequest: AudioFocusRequest? = null

override fun onBind(intent: android.content.Intent?): IBinder? = null

override fun onStartCommand(intent: android.content.Intent?, flags: Int, startId: Int): Int {
Expand Down Expand Up @@ -47,14 +54,52 @@ class CallForegroundService : Service() {
return START_NOT_STICKY
}

acquireWakeLock()
requestAudioFocus()

return START_NOT_STICKY
}

override fun onDestroy() {
releaseAudioFocus()
releaseWakeLock()
stopForeground(STOP_FOREGROUND_REMOVE)
super.onDestroy()
}

private fun acquireWakeLock() {
if (wakeLock?.isHeld == true) return
val pm = getSystemService(POWER_SERVICE) as PowerManager
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Sable:CallForeground")
wakeLock?.acquire(WAKE_LOCK_TIMEOUT_MS)
}

private fun releaseWakeLock() {
wakeLock?.let { if (it.isHeld) it.release() }
wakeLock = null
}

private fun requestAudioFocus() {
val am = getSystemService(AUDIO_SERVICE) as AudioManager
val attributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build()
audioFocusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
.setAudioAttributes(attributes)
.setOnAudioFocusChangeListener { _ -> }
.build()
am.requestAudioFocus(audioFocusRequest!!)
}

private fun releaseAudioFocus() {
audioFocusRequest?.let {
val am = getSystemService(AUDIO_SERVICE) as AudioManager
am.abandonAudioFocusRequest(it)
}
audioFocusRequest = null
}

private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return

Expand All @@ -70,5 +115,6 @@ class CallForegroundService : Service() {
const val CHANNEL_ID = "call_foreground"
const val NOTIFICATION_ID = 1395
const val TAG = "CallForegroundService"
const val WAKE_LOCK_TIMEOUT_MS = 60L * 60L * 1000L
}
}
8 changes: 5 additions & 3 deletions src-tauri/ios-project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ targets:
properties:
LSRequiresIPhoneOS: true
UILaunchStoryboardName: LaunchScreen
UIBackgroundModes: [audio]
UIBackgroundModes: [audio, voip]
NSMicrophoneUsageDescription: Sable uses your microphone for calls.
NSCameraUsageDescription: Sable uses your camera for video calls.
UIRequiredDeviceCapabilities: [arm64, metal]
Expand All @@ -107,7 +107,7 @@ targets:
- UIInterfaceOrientationLandscapeRight
CFBundleShortVersionString: {{apple.bundle-version-short}}
CFBundleVersion: "{{apple.bundle-version}}"
UIBackgroundModes: [audio]
UIBackgroundModes: [audio, voip]
NSPhotoLibraryAddUsageDescription: Sable saves images you download to your photo library.
# Mirrors plugins.deep-link in tauri.conf.json: the plugin only patches
# these in when cargo reruns its build script, so a fresh `tauri ios
Expand Down Expand Up @@ -159,7 +159,9 @@ targets:
- sdk: MetalKit.framework
- sdk: QuartzCore.framework
- sdk: Security.framework
- sdk: UIKit.framework{{#if this.ios-frameworks}}{{~#each ios-frameworks}}
- sdk: UIKit.framework
- sdk: CallKit.framework
- sdk: AVFoundation.framework{{#if this.ios-frameworks}}{{~#each ios-frameworks}}
- sdk: {{this}}.framework{{/each}}{{/if}}
- sdk: WebKit.framework
preBuildScripts:
Expand Down
Loading
Loading