Skip to content
Open
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
8 changes: 4 additions & 4 deletions application/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ dependencies:

# Temporary solution, to be removed after the release of chat/live
dependency_overrides:
rtc_room_engine: 4.1.0
live_uikit_barrage: 3.0.1
atomic_x_core: ^5.0.0
rtc_room_engine: ^4.2.0
live_uikit_barrage: ^3.0.2
atomic_x_core: ^5.0.2
tencent_cloud_chat_sdk: ^9.0.7652

dev_dependencies:
Expand Down Expand Up @@ -130,4 +130,4 @@ flutter:
# For details regarding fonts from package dependencies,
# see https://flutter.dev/to/font-from-package
flutter_intl:
enabled: true
enabled: true
29 changes: 29 additions & 0 deletions atomic-x/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/

# IntelliJ related
*.iml
*.ipr
*.iws
.idea/

# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/

# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
build/
6 changes: 6 additions & 0 deletions atomic-x/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 5.1.0

- Add foreground service
- Add two new permission statuses: limited and notDetermined
- Fix other known issues

## 5.0.0

### Breaking changes
Expand Down
21 changes: 20 additions & 1 deletion atomic-x/android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
<!-- System alert window / Display over other apps permission -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />

<application>
<activity
android:name="io.trtc.tuikit.atomicx.albumpicker.AlbumPickerHostActivity"
Expand Down Expand Up @@ -53,6 +58,20 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/imageuploader_file_paths" />
</provider>

<service
android:name="io.trtc.tuikit.atomicx.foregroundservice.AudioForegroundService"
android:exported="false"
android:foregroundServiceType="microphone" />

<service
android:name="io.trtc.tuikit.atomicx.foregroundservice.VideoForegroundService"
android:exported="false"
android:foregroundServiceType="camera|microphone" />

<service
android:name="io.trtc.tuikit.atomicx.foregroundservice.MediaForegroundService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="mediaPlayback" />
</application>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ class AlbumPickerHandler(
)
textMessage?.let { event["textMessage"] = it }
capturedSessionId?.let { event["sessionId"] = it }
Log.d(TAG, "onPickConfirm dispatching event to Flutter (sessionId=$capturedSessionId)")
mainHandler.post { eventSink(event) }
} catch (e: Exception) {
Log.e(TAG, "Error building onPickConfirm event", e)
Expand Down Expand Up @@ -216,10 +217,12 @@ class AlbumPickerHandler(

override fun onMediaProcessed() {
Log.d(TAG, "onMediaProcessed")
val event = mutableMapOf<String, Any>("type" to "onMediaProcessed")
capturedSessionId?.let { event["sessionId"] = it }
mainHandler.post { eventSink(event) }
completeSession()
executor.execute {
val event = mutableMapOf<String, Any>("type" to "onMediaProcessed")
capturedSessionId?.let { event["sessionId"] = it }
mainHandler.post { eventSink(event) }
mainHandler.post { completeSession() }
}
}

override fun onCancel() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.trtc.tuikit.atomicx.foregroundservice

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
import androidx.core.content.ContextCompat
import io.trtc.tuikit.atomicx.foregroundservice.base.BaseForegroundService
import io.trtc.tuikit.atomicx.foregroundservice.base.ServiceLauncher

class AudioForegroundService : BaseForegroundService(launcher) {

override fun provideForegroundServiceType() = FOREGROUND_SERVICE_TYPE_MICROPHONE

override fun provideChannelId() = "rtc_uikit_audio_foreground_service"

companion object {
internal val launcher =
ServiceLauncher(AudioForegroundService::class.java, "AudioForegroundService")

@JvmStatic
fun start(context: Context, title: String?, description: String?, icon: Int) {
launcher.start(context, title, description, icon) {
ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED
}
}

@JvmStatic
fun stop(context: Context) = launcher.stop(context)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.trtc.tuikit.atomicx.foregroundservice

import android.content.Context
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
import io.trtc.tuikit.atomicx.foregroundservice.base.BaseForegroundService
import io.trtc.tuikit.atomicx.foregroundservice.base.ServiceLauncher

class MediaForegroundService : BaseForegroundService(launcher) {

override fun provideForegroundServiceType() = FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK

override fun provideChannelId() = "rtc_uikit_media_foreground_service"

companion object {
internal val launcher =
ServiceLauncher(MediaForegroundService::class.java, "MediaForegroundService")

@JvmStatic
fun start(context: Context, title: String?, description: String?, icon: Int) {
launcher.start(context, title, description, icon) { true }
}

@JvmStatic
fun stop(context: Context) = launcher.stop(context)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package io.trtc.tuikit.atomicx.foregroundservice

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
import android.os.Build
import androidx.core.content.ContextCompat
import io.trtc.tuikit.atomicx.foregroundservice.base.BaseForegroundService
import io.trtc.tuikit.atomicx.foregroundservice.base.ServiceLauncher

class VideoForegroundService : BaseForegroundService(launcher) {

override fun provideForegroundServiceType(): Int {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
FOREGROUND_SERVICE_TYPE_MICROPHONE or FOREGROUND_SERVICE_TYPE_CAMERA
} else 0
}

override fun provideChannelId() = "rtc_uikit_video_foreground_service"

companion object {
internal val launcher =
ServiceLauncher(VideoForegroundService::class.java, "VideoForegroundService")

@JvmStatic
fun start(context: Context, title: String?, description: String?, icon: Int) {
launcher.start(context, title, description, icon) {
val hasCameraPermission =
ContextCompat.checkSelfPermission(
context,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
val hasAudioPermission =
ContextCompat.checkSelfPermission(
context,
Manifest.permission.RECORD_AUDIO
) == PackageManager.PERMISSION_GRANTED
hasCameraPermission && hasAudioPermission
}
}

@JvmStatic
fun stop(context: Context) = launcher.stop(context)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package io.trtc.tuikit.atomicx.foregroundservice.base

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Intent
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
import io.trtc.tuikit.atomicx.basecomponent.utils.ContextProvider
import io.trtc.tuikit.atomicx.R
import io.trtc.tuikit.atomicx.util.TUIBuild

abstract class BaseForegroundService(
private val launcher: ServiceLauncher<*>
) : Service() {

companion object {
const val TITLE = "title"
const val ICON = "icon"
const val DESCRIPTION = "description"
const val NOTIFICATION_ID = 1001
}

abstract fun provideForegroundServiceType(): Int

abstract fun provideChannelId(): String

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent == null) return START_NOT_STICKY

val appContext = ContextProvider.appContext
val title = intent.getStringExtra(TITLE)
val description = intent.getStringExtra(DESCRIPTION)
val icon = intent.getIntExtra(ICON, appContext?.applicationInfo?.icon ?: 0)

val notification = createNotification(title, description, icon)

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
startForeground(NOTIFICATION_ID, notification, provideForegroundServiceType())
} else {
startForeground(NOTIFICATION_ID, notification)
}

if (launcher.state == ServiceState.STOPPING) {
stopSelf()
launcher.updateState(ServiceState.IDLE)
} else {
launcher.updateState(ServiceState.RUNNING)
}
return START_NOT_STICKY
}

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

override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
stopSelf()
launcher.updateState(ServiceState.IDLE)
}

override fun onDestroy() {
super.onDestroy()
clearNotification()
}

private fun createNotification(title: String?, desc: String?, icon: Int): Notification {
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
val channelId = provideChannelId()
if (TUIBuild.getVersionInt() >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
channelId,
applicationContext.getString(R.string.common_rtc_channel_name),
NotificationManager.IMPORTANCE_LOW
)
manager.createNotificationChannel(channel)
}
return NotificationCompat.Builder(this, channelId)
.setSmallIcon(icon)
.setContentTitle(title)
.setContentText(desc)
.build()
}

private fun clearNotification() {
val manager = getSystemService(NOTIFICATION_SERVICE) as? NotificationManager
manager?.cancel(NOTIFICATION_ID)
}
}
Loading