Kebijakan Privasi
Perjanjian Pemrosesan dan Keamanan Data

Module | Description |
Core call view component. Automatically observes CallStore data and renders video, while supporting UI customization such as layout switching, avatar and icon configuration. | |
Manages the call lifecycle: initiate, answer, reject, hang up. Provides real-time access to participant audio/video status, call timer, call records, and more. | |
Controls audio/video devices: microphone (toggle/volume), camera (toggle/switch/quality), screen sharing, and real-time device status monitoring. |
api "io.trtc.uikit:atomicx-core:latest.release" and api "com.tencent.imsdk:imsdk-plus:8.7.7201" to your build.gradle file, then run Gradle Sync.dependencies {api 'io.trtc.uikit:atomicx-core:latest.release'api "com.tencent.imsdk:imsdk-plus:8.7.7201"// other dependencies...}

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// Initialize CallStoreCallStore.sharedval sdkAppId = 1400000001 // Replace with your SDKAppIDval userId = "test_001" // Replace with your UserIDval userSig = "xxxxxxxxxxx" // Replace with your UserSigLoginStore.shared.login(this,sdkAppId,userId,userSig,object : CompletionHandler {override fun onSuccess() {// Complete TUICallEngine initializationTUICallEngine.createInstance(context).init(GenerateTestUserSig.SDKAPPID, userId, userSig, null)// Handle login successLog.d("Login", "login success");}override fun onFailure(code: Int, desc: String) {// Handle login failureLog.e("Login", "login failed, code: $code, error: $desc");}})}}
Parameter | Type | Description |
userId | String | Unique identifier for the current user. Only English letters, numbers, hyphens, and underscores are allowed. Avoid simple IDs (e.g., 1, 123) to prevent multi-device login conflicts. |
sdkAppId | int | |
userSig | String | Authentication token for TRTC. Development: Use GenerateTestUserSig.genTestUserSig or UserSig Tool to generate a temporary UserSig. Production: Always generate UserSig server-side to prevent secret key leakage. See Server-side UserSig Generation for details. For more info, see How to Calculate and Use UserSig. |
import io.trtc.tuikit.atomicxcore.api.view.CallCoreViewclass CallActivity : AppCompatActivity() {private var callCoreView: CallCoreView? = null// 1. Create the call page containeroverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 2. Bind CallCoreView to the call pagecallCoreView = CallCoreView(this)setContentView(callCoreView)}}
Feature | Description | Reference |
Set Layout Mode | Switches layout modes flexibly. If not set, adapts layout automatically based on participant count. | Layout mode switching |
Set Avatar | Allows custom avatars for users by providing avatar resource paths. | Custom default avatar |
Set Volume Indicator Icon | Supports custom volume indicator icons for different volume levels. | Custom volume indicator icon |
Set Network Indicator Icon | Configures network status indicator icons based on real-time network quality. | Custom network indicator icon |
Set Waiting Animation for Users | In multi-party calls, supports GIF animations for users waiting to answer. | Custom loading animation |
import io.trtc.tuikit.atomicxcore.api.device.DeviceStoreimport io.trtc.tuikit.atomicxcore.api.call.CallStoreclass CallActivity : AppCompatActivity() {private var buttonContainer: LinearLayout? = nullprivate var buttonAccept: Button? = nullprivate var buttonReject: Button? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 1. Create the bottom button bar containercreateButtonContainer()// 2. Add "Answer" and "Reject" buttonsaddRejectAndAcceptButtons()setContentView(buttonContainer)}// Create the bottom button bar containerprivate fun createButtonContainer() {buttonContainer = LinearLayout(this).apply {orientation = LinearLayout.HORIZONTALgravity = Gravity.CENTERlayoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,FrameLayout.LayoutParams.WRAP_CONTENT).apply {gravity = Gravity.BOTTOMbottomMargin = dpToPx(80)}}}// Add "Answer" and "Reject" buttonsprivate fun addRejectAndAcceptButtons() {createAcceptButton()createRejectButton()buttonContainer?.addView(buttonAccept)buttonContainer?.addView(buttonReject)}// Create the Answer buttonprivate fun createAcceptButton() {buttonAccept = Button(this).apply {text = "Answer"layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f).apply {marginEnd = dpToPx(8)}setOnClickListener {// 3. Bind accept to Answer button click eventCallStore.shared.accept(null)}}}// Create the Reject buttonprivate fun createRejectButton() {buttonReject = Button(this).apply {text = "Reject"layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f).apply {marginStart = dpToPx(8)}setOnClickListener {// 3. Bind reject to Reject button click eventCallStore.shared.reject(null)}}}}
import io.trtc.tuikit.atomicxcore.api.call.CallEndReasonimport io.trtc.tuikit.atomicxcore.api.call.CallListenerimport io.trtc.tuikit.atomicxcore.api.call.CallMediaTypeimport io.trtc.tuikit.atomicxcore.api.call.CallStoreclass CallActivity : AppCompatActivity() {private var callListener: CallListener? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// ... other initialization code// 1. Listen for call ended eventsaddListener()}private fun addListener() {callListener = object : CallListener() {override fun onCallEnded(callId: String, mediaType: CallMediaType, reason: CallEndReason, userId: String) {// 2. Close the page when the call endsfinish()}}callListener?.let { CallStore.shared.addListener(it) }}}
Parameter | Type | Description |
callId | String | Unique identifier for this call. |
mediaType | Specifies whether it's an audio or video call. - CallMediaType.Video: Video call. - CallMediaType.Audio: Audio call. | |
reason | Reason for call ending. - Unknown: Unknown reason. - Hangup: User hung up. - Reject: Callee rejected. - NoResponse: Callee did not answer in time. - Offline: Other party offline. - LineBusy: Other party busy. - Canceled: Caller canceled before callee answered. - OtherDeviceAccepted: Answered on another device. - OtherDeviceReject: Rejected on another device. - EndByServer: Ended by server. | |
userId | String | User ID that triggered the end. |
AndroidManifest.xml.<manifest xmlns:android="http://schemas.android.com/apk/res/android"><!-- Microphone permission --><uses-permission android:name="android.permission.RECORD_AUDIO" /><!-- Camera permission --><uses-permission android:name="android.permission.CAMERA" /><application><!-- ... --><activityandroid:name=".CallActivity"android:exported="false"android:screenOrientation="portrait" /></application></manifest>
// Dynamically request permissionsprivate fun requestAllCallPermissions() {val allPermissions = arrayOf(Manifest.permission.CAMERA,Manifest.permission.RECORD_AUDIO)if (!checkPermissions(allPermissions)) {ActivityCompat.requestPermissions(this, allPermissions, PERMISSION_REQUEST_CODE)}}// Handle permission request resultoverride fun onRequestPermissionsResult(requestCode: Int,permissions: Array<out String>,grantResults: IntArray) {super.onRequestPermissionsResult(requestCode, permissions, grantResults)if (requestCode == PERMISSION_REQUEST_CODE) {// Check if all permissions are grantedvar allGranted = truefor (result in grantResults) {if (result != PackageManager.PERMISSION_GRANTED) {allGranted = falsebreak}}if (allGranted) {Log.d("MainActivity", "Permission granted")} else {Log.w("MainActivity", "Some permissions denied")}}}
CallStore.observerState.selfInfo for the current user's status.SelfInfo.Status is CallParticipantStatus.Waiting, play ringtone/vibration. If status is CallParticipantStatus.Accept, stop the notification.import kotlinx.coroutines.CoroutineScopeimport kotlinx.coroutines.Dispatchersimport kotlinx.coroutines.Jobimport kotlinx.coroutines.launchclass MainActivity : AppCompatActivity() {private var stateJob: Job? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 1. Monitor current user's call statusobserveSelfStatus()}// Monitor current user's call statusprivate fun observeSelfStatus() {stateJob = CoroutineScope(Dispatchers.Main).launch {CallStore.shared.observerState.selfInfo.collect { selfInfo ->// 2. Play/stop incoming call notificationif (selfInfo.status == CallParticipantStatus.Waiting) {// Start playing incoming call notification}if (selfInfo.status == CallParticipantStatus.Accept) {// Stop playing incoming call notification}}}}}
onCallReceived event.import io.trtc.tuikit.atomicxcore.api.device.DeviceStoreimport io.trtc.tuikit.atomicxcore.api.call.CallMediaTypeimport io.trtc.tuikit.atomicxcore.api.call.*class MainActivity : AppCompatActivity() {private var callListener: CallListener? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// ... other initialization code// 1. Listen for incoming call eventaddListener()}private fun addListener() {callListener = object : CallListener() {override fun onCallReceived(callId: String, mediaType: CallMediaType, userData: String) {super.onCallReceived(callId, mediaType, userData)// 2. Open devices based on media typeopenDeviceForMediaType(mediaType)}}callListener?.let { CallStore.shared.addListener(it) }}private fun openDeviceForMediaType(mediaType: CallMediaType?) {mediaType?.let {DeviceStore.shared().openLocalMicrophone(null)if (mediaType == CallMediaType.Video) {val isFrontCamera = trueDeviceStore.shared().openLocalCamera(isFrontCamera, null)}}}}
Parameter | Type | Description |
callId | String | Unique identifier for this call. |
mediaType | Specifies whether it's an audio or video call. CallMediaType.Video: Video call.CallMediaType.Audio: Audio call. |
Parameter Name | Type | Required | Description |
isFront | Boolean | Yes | Whether to open the front camera. true: Open front camera. false: Open rear camera. |
completion | CompletionHandler | No | Callback for operation result; returns error code/message if failed. |
Parameter Name | Type | Required | Description |
completion | CompletionHandler | No | Callback for operation result; returns error code/message if failed. |
private fun addListener() {callListener = object : CallListener() {override fun onCallReceived(callId: String, mediaType: CallMediaType, userData: String) {super.onCallReceived(callId, mediaType, userData)// Trigger call pageval intent = Intent(this@MainActivity, CallActivity::class.java)startActivity(intent)}}callListener?.let { CallStore.shared.addListener(it) }}


private fun setIconResourcePath() {val volumeLevelIcons = mapOf(VolumeLevel.Mute to "icon resource path")val callCoreView = CallCoreView(context)callCoreView.setVolumeLevelIcons(volumeLevelIcons)}
Parameter | Type | Required | Description |
icons | Map | Yes | Mapping of volume levels to icon resources. Key (VolumeLevel): VolumeLevel.Mute: Microphone muted. VolumeLevel.Low: Volume (0-25].VolumeLevel.Medium: Volume (25-50].VolumeLevel.High: Volume (50-75].VolumeLevel.Peak: Volume (75-100].Value (String): Icon resource path. |
Icon | Description | Download Link |
![]() | Volume indicator icon. Recommended for VolumeLevel.Low or VolumeLevel.Medium. | |
![]() | Mute icon. Recommended for VolumeLevel.Mute. |

private fun setNetworkQualityIcons() {val volumeLevelIcons = mapOf(NetworkQuality.BAD to "icon path")val callCoreView = CallCoreView(context)callCoreView.setNetworkQualityIcons(volumeLevelIcons)}
Parameter | Type | Required | Description |
icons | Map | Yes | Mapping of network quality levels to icon resources. Key (NetworkQuality): NetworkQuality.UNKNOWN: UnknownNetworkQuality.EXCELLENT: ExcellentNetworkQuality.GOOD: Good NetworkQuality.POOR: PoorNetworkQuality.BAD: Bad NetworkQuality.VERY_BAD: Very badNetworkQuality.DOWN: Disconnected Value (String): Icon resource path. |
Icon | Description | Download Link |
![]() | Poor network indicator icon. Recommended for NetworkQuality.BAD, NetworkQuality.VERY_BAD, or NetworkQuality.DOWN. |
private fun setParticipantAvatars() {val avatars: MutableMap<String, ParticipantAvatarInfo> = mutableMapOf()val userId = "" // User IDval avatarPath = "" // Default avatar resource pathavatars[userId] = avatarPathval callCoreView = CallCoreView(context)callCoreView.setParticipantAvatars(avatars)}
Parameter | Type | Required | Description |
icons | Map | Yes | Mapping of user IDs to avatar resource paths. - Key: userID. - Value: Absolute path to avatar resource. |
Icon | Description | Download Link |
![]() | Default avatar. Recommended when user avatar is missing or fails to load. |

private fun setWaitingAnimation() {val waitingAnimationPath = "" // Path to the waiting animation GIF resourceval callCoreView = CallCoreView(context)callCoreView.setWaitingAnimation(waitingAnimationPath)}
Parameter | Type | Required | Description |
path | String | Yes | Absolute path to the GIF resource. |
Icon | Description | Download Link |
![]() | Waiting animation for users. Recommended for group calls when user status is waiting to answer. |
CallStore.observerState.activeCall for the current active call.activeCall.duration to your UI control. This field updates reactively—no need to manually manage a timer.import android.content.Contextimport androidx.appcompat.widget.AppCompatTextViewimport androidx.core.content.ContextCompatimport com.tencent.qcloud.tuicore.util.DateTimeUtilimport io.trtc.tuikit.atomicx.Rimport io.trtc.tuikit.atomicxcore.api.call.CallStoreimport io.trtc.tuikit.atomicxcore.api.call.CallParticipantStatusimport kotlinx.coroutines.CoroutineScopeimport kotlinx.coroutines.Dispatchersimport kotlinx.coroutines.Jobimport kotlinx.coroutines.launchclass TimerView(context: Context) : AppCompatTextView(context) {private var subscribeStateJob: Job? = nulloverride fun onAttachedToWindow() {super.onAttachedToWindow()// 1. Subscribe to activeCall in the data layerregisterActiveCallObserver()}override fun onDetachedFromWindow() {super.onDetachedFromWindow()subscribeStateJob?.cancel()}private fun registerActiveCallObserver() {subscribeStateJob = CoroutineScope(Dispatchers.Main).launch {CallStore.shared.observerState.activeCall.collect { activeCall ->// 2. Bind call timer data, update call durationupdateDurationView(activeCall)}}}private fun updateDurationView(activeCall: CallInfo) {val currentDuration = activeCall.durationtext = DateTimeUtil.formatSecondsTo00(currentDuration.toInt())}}
val user = UserProfile()user.userID = "" // Your userIduser.avatarURL = "" // Avatar urluser.nickname = "" // Nickname to setLoginStore.shared.setSelfInfo(user, object : CompletionHandler {override fun onSuccess() {// Success callback}override fun onFailure(code: Int, desc: String) {// Failure callback}})
Parameter | Type | Required | Description |
userProfile | Yes | User information struct. userID (String): User ID.avatarURL (String): Avatar URL. nickname (String): User nickname. See UserProfile for more fields. | |
completion | CompletionHandler | No | Callback for operation result. |
Float Mode | Grid Mode | PIP Mode |
![]() | ![]() | ![]() |
Layout Logic: Full screen shows own video while waiting; after answering, full screen shows the other party's video, own video floats as a small window. Interaction: Supports dragging and swapping windows. | Layout Logic: All participant videos are tiled in a grid. Suitable for 2+ participants; supports click-to-enlarge. Interaction: Click to enlarge a participant's video. | Layout Logic: In 1v1, always shows the other party's video; in multi-party, uses active speaker strategy. Interaction: Shows own video while waiting, displays call timer after answering. |
private fun setLayoutTemplate() {val callCoreView = CallCoreView()val template = CallLayoutTemplate.Grid// Set layout modecallCoreView.setLayoutTemplate(template)setContentView(callCoreView)}
Parameter | Type | Required | Description |
template | Yes | CallCoreView's layout mode CallLayoutTemplate.float :Layout: While waiting, display your own video full screen. After answering, show the remote video full screen and your own video as a floating window. Interaction: Drag the small window or tap to swap big/small video. CallLayoutTemplate.grid :Layout: All participant videos are tiled in a grid. Best for 2+ participants. Tap to enlarge a video. Interaction: Tap a participant to enlarge their video. CallLayoutTemplate.pip : Layout: In 1v1, remote video is fixed; in multi-party, the active speaker is shown full screen. Interaction: Shows your own video while waiting, displays call timer after answering. |
enterPictureInPictureMode. private fun enterPictureInPictureModeWithBuild() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && hasPipModePermission()) {val pictureInPictureParams: PictureInPictureParams.Builder = PictureInPictureParams.Builder()val floatViewWidth = resources.getDimensionPixelSize(R.dimen.callkit_video_small_view_width)val floatViewHeight = resources.getDimensionPixelSize(R.dimen.callkit_video_small_view_height)val aspectRatio = Rational(floatViewWidth, floatViewHeight)pictureInPictureParams.setAspectRatio(aspectRatio).build()this.enterPictureInPictureMode(pictureInPictureParams.build())}}
onPictureInPictureModeChanged callback is triggered. You must update the CallCoreView layout to CallLayoutTemplate.Pip at this stage.val callCoreView = CallCoreView(context)override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {super.onPictureInPictureModeChanged(isInPictureInPictureMode)if (isInPictureInPictureMode) {callCoreView.setLayoutTemplate(CallLayoutTemplate.Pip)}}
CallLayoutTemplate.Float.CallLayoutTemplate.Grid.val callCoreView = CallCoreView(context)override fun onResume() {super.onResume()val allParticipants = CallStore.shared.observerState.allParticipants.valueif (allParticipants.size > 2) {callCoreView?.setLayoutTemplate(CallLayoutTemplate.Grid)} else {callCoreView?.setLayoutTemplate(CallLayoutTemplate.Float)}}
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON flags.class CallActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// Apply flags to handle screen behavior during callswindow.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or // Show activity over lock screenWindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or // Dismiss non-secure keyguardsWindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or // Prevent the screen from sleepingWindowManager.LayoutParams.FLAG_TURN_SCREEN_ON // Turn the screen on when activity starts)setContentView(R.layout.activity_call)}}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"><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" /><application><serviceandroid:name=".CallForegroundService"android:enabled="true"android:exported="false"android:foregroundServiceType="camera|microphone" /></application></manifest>
import android.app.Notificationimport android.app.NotificationChannelimport android.app.NotificationManagerimport android.app.Serviceimport android.content.Contextimport android.content.Intentimport android.os.Buildimport android.os.IBinderimport androidx.core.app.NotificationCompatclass CallForegroundService : Service() {companion object {private const val NOTIFICATION_ID = 1001private const val CHANNEL_ID = "call_foreground_channel"/*** Start the foreground service to maintain call connectivity*/fun start(context: Context) {val intent = Intent(context, CallForegroundService::class.java)if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {context.startForegroundService(intent)} else {context.startService(intent)}}/*** Stop the service once the call ends*/fun stop(context: Context) {val intent = Intent(context, CallForegroundService::class.java)context.stopService(intent)}}override fun onCreate() {super.onCreate()createNotificationChannel()// Start foreground with notification to secure background capture permissionsstartForeground(NOTIFICATION_ID, createNotification())}override fun onBind(intent: Intent?): IBinder? = nullprivate fun createNotification(): Notification {return NotificationCompat.Builder(this, CHANNEL_ID).setContentTitle("Call in Progress").setContentText("App is running in the background to maintain the call.").setSmallIcon(android.R.drawable.ic_menu_call) // Replace with your app icon.setPriority(NotificationCompat.PRIORITY_HIGH).build()}private fun createNotificationChannel() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {val channel = NotificationChannel(CHANNEL_ID,"Call Keep-Alive Service",NotificationManager.IMPORTANCE_HIGH)val manager = getSystemService(NotificationManager::class.java)manager?.createNotificationChannel(channel)}}}
<activityandroid:name=".view.CallActivity"android:launchMode="singleTask"android:taskAffinity="${applicationId}.call" />
Apakah halaman ini membantu?
Anda juga dapat Menghubungi Penjualan atau Mengirimkan Tiket untuk meminta bantuan.
masukan