이 문서에서는 Android 애플리케이션에서 SDK를 통합하고 맞춤설정하는 방법을 설명합니다.
시작하기
Android용 헤드리스 모바일 SDK를 사용하면 CCAI 플랫폼의 고객센터 기능을 자체 내장 Android UI에 통합할 수 있습니다.
CCAI Platform 브랜드 위젯을 표시하는 대신 다음을 수행합니다.
CCAI Android SDK 모듈을 종속 항목으로 추가합니다.
Kotlin (권장) 또는 Java로 자체 화면, 흐름, 시각적 디자인을 빌드합니다.
SDK를 사용하여 다음 작업을 할 수 있습니다.
채팅 및 음성 세션을 시작하고 관리합니다.
이메일, 음성 통화, 전환 옵션을 제공합니다.
스마트 작업, 첨부파일, 세션 후 흐름 (CSAT, 설문조사, 가상 에이전트) 지원
CCAI Platform은 고객센터 로직 (라우팅, 대기열, 채널, 구성, 보고)을 처리합니다. 전체 앱 환경 (UI, 흐름, 브랜딩)을 소유합니다.
구성 가능한 테마가 있는 사전 빌드된 UI를 선호하는 경우 표준 모바일 SDK를 사용하세요.
요구사항 및 지원되는 환경
이 섹션에서는 Android용 헤드리스 모바일 SDK의 요구사항과 지원되는 환경을 설명합니다.
Android 플랫폼 요구사항
Android에는 다음 플랫폼 요구사항이 적용됩니다.
최소 Android 버전: Android 6.0 (API 수준 23) 이상
컴파일 SDK: 36 (권장)
지원되는 언어: Kotlin (권장) 또는 Java
Java 호환성: Java 17 이상
Gradle / Android 스튜디오: 앱 및 사용 중인 SDK 버전과 호환되는 최신 Android Gradle 플러그인 및 Android 스튜디오 버전
Kotlin 버전: 1.6.0 이상
CCAI Platform 인스턴스 요구사항
다음 정보를 얻으려면 CCAI Platform 인스턴스의 개발자 설정에 액세스해야 합니다.
회사 키
회사 비밀 코드
호스트 URL: CCAI 플랫폼의 호스트 이름 (예: your_subdomain.ccaiplatform.com)
이러한 값은 다음 용도로 사용됩니다.
회사 키 및 호스트 URL: Android 앱에서 SDK를 초기화합니다.
회사 비밀 코드: 백엔드에서 사용자를 인증하는 데 사용되는 JSON 웹 토큰(JWT)에 서명합니다.
네트워킹 및 권한
앱에서 CCAI Platform 엔드포인트와 구성된 음성 제공업체 엔드포인트로의 아웃바운드 트래픽을 허용해야 합니다.
일반적인 Android 권한에는 다음이 포함됩니다 (실제 목록은 SDK 버전과 특성 세트에 따라 다를 수 있음).
인터넷 / 네트워크 상태: 모든 SDK 통신에 사용됩니다.
마이크: 음성 통화에 사용됩니다.
알림: Firebase 클라우드 메시징 (FCM) 푸시 알림
저장소 / 미디어: 첨부파일용
카메라: 스마트 액션에서 사진 또는 동영상 캡처
화면 캡처: 화면 공유 (
MediaProjection)
Android OS 가이드라인에 따라 런타임 권한을 선언하고 요청합니다.
푸시 알림
헤드리스 Android SDK는 FCM을 사용하여 다음을 제공합니다.
수신 전화 알림
특정 스마트 작업 및 통화 관련 메시지
앱이 백그라운드로 전환될 때 상태를 유지하는 데 도움이 되는 업데이트
필요한 사항은 다음과 같습니다.
Firebase 프로젝트
앱 모듈에 유효한 google-services.json
앱의 FCM 토큰 처리 및 등록
앱의 맞춤 알림 및 통화 중 UI
헤드리스 Android SDK가 앱에 적합한 이유
Android용 헤드리스 모바일 SDK는 세 가지 레이어로 볼 수 있습니다.
CCAI Platform 플랫폼 및 SDK (CCAI Platform에서 처리)
SDK는 CCAI Platform 플랫폼과 안전하게 상호작용하며 다음을 제공합니다.
JSON 웹 토큰 (JWT)을 사용한 인증
대기열 및 채널 라우팅 메타데이터 (채팅, 음성 통화, 이메일, 외부 전환 링크)
서비스 객체 (
chatService,queueMenuService등)를 사용한 실시간 세션 상태 관리스마트 작업, 파일 첨부, 화면 공유를 위한 보안 전송 파이프라인입니다.
전환 로직 및 세션 후 계층 구조 평가
Android 앱 (UI 및 로직 - 개발자가 처리)
시각적 및 탐색 환경을 완전히 제어할 수 있습니다.
진입점: 예를 들어 도움말 탭 또는 문의하기 버튼
메뉴: 대기열이 표시되는 방식 (예: 결제, 기술 지원)
세션 내 UI: 맞춤 채팅 풍선, 통화 화면, 컨트롤
앱의 기능: SDK 서비스 API를 호출하여 세션을 시작하고 종료하며, Kotlin 코루틴과 흐름을 사용하여 SDK 이벤트를 관찰하고, 구성요소 탐색을 유도합니다.
CCAI Platform 관리 포털 구성
CCAI Platform 관리자는 응대 규칙 (운영 시간, 사용 가능한 채널, 대기 시간 기준, 설문조사)을 구성합니다. SDK는 이 구성을 읽고 앱에 원시 데이터와 이벤트로 노출합니다. 화면에 해당 구성을 그리는 방법을 결정합니다.
회사 사용자 인증 정보 가져오기
SDK를 통합하기 전에 CCAI Platform 인스턴스에서 사용자 인증 정보를 가져옵니다.
관리자 계정으로 CCAI Platform 관리 포털에 로그인합니다.
설정 > 개발자 설정으로 이동합니다.
회사 키 및 보안 코드에서 다음을 복사합니다.
회사 키
회사 비밀 코드
호스트 URL (CCAI 플랫폼의 호스트 이름, 예:
your_subdomain.ccaiplatform.com)을 기록해 둡니다.
다음 항목을 사용합니다.
Android 앱: 회사 키 및 호스트 URL
백엔드 서버: 최종 사용자 인증을 위해 JWT에 서명하는 회사 비밀 코드와 CCAI Platform에서 사용하는 선택적 맞춤 데이터 및 컨텍스트입니다.
Android 앱에 SDK 추가
헤드리스 SDK는 모듈식 아키텍처를 사용합니다. 앱에 필요한 특정 기능 모듈 (예: CCAIChat 또는 CCAIScreenShare)과 함께 핵심 CCAIKit를 설치합니다.
CCAI Maven 저장소 추가
CCAI Maven 저장소를 프로젝트의 settings.gradle.kts (또는 루트 build.gradle)에 추가합니다.
// settings.gradle.kts
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url = uri("https://sdk.ujet.co/ccaip/android/") }
}
}
Gradle 버전 카탈로그 구성 (권장)
Google Cloud 는 SDK 종속 항목 관리에 Gradle 버전 카탈로그를 사용할 것을 권장합니다. gradle/libs.versions.toml에 다음을 추가합니다.
[versions]
ccaiVersion = "3.3.1"
[libraries]
ccai-kit = { group = "com.ccaiplatform.android", name = "CCAIKit", version.ref = "ccaiVersion" }
ccai-chat = { group = "com.ccaiplatform.android", name = "CCAIChat", version.ref = "ccaiVersion" }
ccai-chat-red = { group = "com.ccaiplatform.android", name = "CCAIChatRed", version.ref = "ccaiVersion" }
ccai-call = { group = "com.ccaiplatform.android", name = "CCAICall", version.ref = "ccaiVersion" }
ccai-call-red = { group = "com.ccaiplatform.android", name = "CCAICallRed", version.ref = "ccaiVersion" }
ccai-screenshare = { group = "com.ccaiplatform.android", name = "CCAIScreenShare", version.ref = "ccaiVersion" }
종속 항목 추가
앱 수준 build.gradle.kts에서 핵심 SDK와 특정 기능 모듈을 추가합니다.
// app/build.gradle.kts
android {
compileSdk = 36
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
}
dependencies {
// 1. Core (always required)
implementation(libs.ccai.kit)
// 2. Chat (required for chat sessions)
implementation(libs.ccai.chat)
implementation(libs.ccai.chat.red) // media layer paired with CCAIChat
// 3. Voice (required for instant calls, voicemail, and scheduled calls)
implementation(libs.ccai.call)
implementation(libs.ccai.call.red) // media/transport layer paired with CCAICall
// 4. Screen Share (optional)
// implementation(libs.ccai.screenshare)
}
버전 카탈로그를 사용하지 않는 경우 종속 항목을 직접 선언할 수 있습니다.
val ccaiVersion = "3.3.1"
dependencies {
implementation("com.ccaiplatform.android:CCAIKit:$ccaiVersion")
implementation("com.ccaiplatform.android:CCAIChat:$ccaiVersion")
implementation("com.ccaiplatform.android:CCAIChatRed:$ccaiVersion")
implementation("com.ccaiplatform.android:CCAICall:$ccaiVersion") // Voice calls
implementation("com.ccaiplatform.android:CCAICallRed:$ccaiVersion") // Twilio VoIP backend
implementation("com.ccaiplatform.android:CCAIScreenShare:$ccaiVersion") // Optional, for screen share
}
초기화 및 설정
앱 실행 중에 SDK를 초기화합니다. SDK의 모듈식 아키텍처로 인해 먼저 핵심 CCAI 플랫폼 시스템을 초기화한 다음 특정 채널 제공자 (예: chat 또는 screen share)를 등록합니다.
CCAIDelegate 인터페이스 구현
SDK는 인증 콜백에 CCAIDelegate 인터페이스를 사용합니다. 핵심 메서드는 SDK가 JWT가 필요할 때 호출하는 Kotlin 정지 함수 ccaiShouldAuthenticate()입니다.
import com.ccaiplatform.ccaikit.CCAIDelegate
class MyCCAIDelegate : CCAIDelegate {
/**
* Called by the SDK when it needs a signed JWT for authentication.
* This is a suspend function - you can make network calls here.
*
* @return The auth token returned by `authService.authenticate(jwt)`,
* or null on failure. This is what the SDK expects - not the raw JWT.
*/
override suspend fun ccaiShouldAuthenticate(): String? {
return try {
// 1. Call your backend server to get a signed JWT
val jwt = MyBackendApi.getSignedJwt() ?: return null
// 2. Exchange the JWT for an auth token using authService - this is
// what the SDK expects to be returned (not the raw JWT).
CCAI.authService?.authenticate(jwt)
} catch (e: Exception) {
null // Return null to signal authentication failure
}
}
}
위 스니펫은 최소 2단계 흐름 (서명 → 인증 → 반환)을 보여줍니다.
다음 내용을 비롯한 전체 인증 계약은 최종 사용자 인증 및 맞춤 데이터 전달을 참고하세요.
- 백엔드에서 JWT에 서명하는 방법
authenticate(jwt)가 이를 인증 토큰으로 교환하는 방법- 토큰 캐싱 및 무효화,
- 오류 처리가 포함된 작업 예시
애플리케이션 클래스에서 SDK 초기화
CCAI 싱글톤 객체를 사용하여 맞춤 애플리케이션 클래스에서 SDK를 초기화합니다.
import android.app.Application
import com.ccaiplatform.ccaikit.CCAI
import com.ccaiplatform.ccaikit.InitOptions
import com.ccaiplatform.ccaichat.initializeChat
import com.ccaiplatform.ccaichat.model.ChatOptions
import com.ccaiplatform.ccaicall.initializeCall
import com.ccaiplatform.ccaikit.initializeScreenShare
import com.ccaiplatform.ccaikit.models.screenShare.ScreenShareOptions
class MainApplication : Application() {
private val delegate = MyCCAIDelegate()
override fun onCreate() {
super.onCreate()
// 1. Build InitOptions
val initOptions = InitOptions(
key = "YOUR_COMPANY_KEY",
urlHost = "your_subdomain.ccaiplatform.com",
languageCode = "en", // Optional: ISO 639 code
delegate = delegate // Your delegate implementation
)
// 2. Initialize the core SDK
CCAI.initialize(
context = this,
options = initOptions
)
// 3. Initialize chat (minimal)
CCAI.initializeChat(context = this)
// Initialize chat (with optional configuration)
// val chatOptions = ChatOptions(
// webFormInterface = null, // Implement to intercept and render custom web forms within your own UI
// downloadTranscriptVisibility = DownloadTranscriptVisibility.SHOW_ALL,
// greeting = "Hello! How can I help you?"
// )
// CCAI.initializeChat(
// context = this,
// options = chatOptions
// )
// 4. Initialize call (required whenever your app uses voice or scheduled calls)
CCAI.initializeCall(context = this)
// 5. Initialize screen share (optional)
// CCAI.initializeScreenShare(
// context = this,
// options = ScreenShareOptions(
// key = "YOUR_COMPANY_KEY",
// domain = "your_subdomain.ccaiplatform.com"
// )
// )
}
}
InitOptions 구성 참조
data class InitOptions(
/// The company key used for authentication
var key: String,
/// The host URL for CCAI platform (for example, "my-unique-instance.uc1.ccaiplatform.com")
var urlHost: String,
/// The preferred language code for localization (defaults to "en")
var languageCode: String? = "en",
/// Listener that handles authentication
var delegate: CCAIDelegate? = null,
/// Whether to cache the authentication token (defaults to true)
var cacheAuthToken: Boolean = true
)
AndroidManifest.xml 업데이트
맞춤 애플리케이션 클래스가 등록되어 있는지 확인합니다.
<application
android:name=".MainApplication"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.YourApp">
</application>
사용 가능한 SDK 서비스
초기화 후 CCAI 싱글톤 객체를 통해 다양한 서비스에 액세스할 수 있습니다.
val authService = CCAI.authService
val companyService = CCAI.companyService
val queueMenuService = CCAI.queueMenuService
val optionsService = CCAI.optionsService
val languageService = CCAI.languageService
val pushNotificationService = CCAI.pushNotificationService
val chatService = CCAI.chatService // Only available after initializeChat()
val screenShareService = CCAI.screenShareService // Only available after initializeScreenShare()
val rateService = CCAI.rateService // For CSAT ratings and survey submission
val callService = CCAI.callService // Only available after initializeCall()
회사 구성을 가져오는 중
초기화 후 companyService를 사용하여 회사 수준 구성을 가져올 수 있습니다. 이는 언어 선택기를 채우거나, 회사 이름을 표시하거나, 사용자가 대기열에 들어가기 전에 지원 연락처 세부정보를 읽는 데 유용합니다.
// Fetch company details
val company = CCAI.companyService?.get()
// CompanyResponse contains:
// - displayName: String
// - supportEmail: String?
// - languages: List<String>
// - phoneNumber: String?
// ...
최종 사용자 인증 및 맞춤 데이터 전달
Android용 Headless Mobile SDK는 JSON 웹 토큰 (JWT)을 사용하여 사용자를 인증하고 컨텍스트 정보를 에이전트의 CRM에 안전하게 전달합니다.
작동 방식
SDK는 간소화된 2단계 비동기 인증 흐름을 사용합니다.
SDK에서 사용자를 인증해야 한다고 판단합니다.
정지 함수인
CCAIDelegate메서드(ccaiShouldAuthenticate())를 호출합니다.앱은 `Company
앱은
Company Secret Code를 사용하여 백엔드 서버에서 원격으로 JWT에 서명합니다.그러면 앱이 서명된 JWT를
CCAI.authService?.authenticate(jwt)에 전달하여 인증 토큰으로 교환합니다.인증 토큰이 SDK에 반환되어 연결이 완료됩니다.
인증을 위해 CCAIDelegate 구현
앱은 CCAIDelegate 인터페이스를 구현해야 합니다. 인증이 필요한 경우 SDK는 첫 번째 일시중지 메서드를 호출합니다.
CCAIDelegate 인터페이스
interface CCAIDelegate {
suspend fun ccaiShouldAuthenticate(): String?
}
구현 예 (Kotlin)
class CCAIDelegate : CCAIDelegate {
override suspend fun ccaiShouldAuthenticate(): String? {
// 1. Sign JWT remotely on your backend server
val jwt = signJWTRemotely() ?: return null
// 2. Authenticate JWT using authService to get an auth token
return try {
CCAI.authService?.authenticate(jwt)
} catch (e: Exception) {
null
}
}
}
JWT 서명이 포함된 전체 예시 (참조 및 테스트 전용)
class AuthController : CCAIDelegate {
override suspend fun ccaiShouldAuthenticate(): String? {
// First, sign JWT with company secret (do this on your backend in production)
val jwt = Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS384, companySecret?.encodeToByteArray())
.compact()
// Then authenticate JWT using authService to get an auth token
return try {
CCAI.authService?.authenticate(jwt)
} catch (e: Exception) {
null
}
}
}
CRM에 맞춤 데이터 전달
컨텍스트 데이터를 에이전트에 전달하려면 (예: 사용자의 현재 기기 OS, 위치 또는 계정 등급) 백엔드에서 서명하기 전에 custom_data 객체를 JWT 페이로드에 삽입해야 합니다.
각 맞춤 데이터는 라벨 (상담사가 보는 내용), 값, 유형을 포함하는 JSON 객체로 형식이 지정되어야 합니다.
지원되는 데이터 유형
string: 표준 텍스트 (예: 'Pixel 8 Pro')number: 정수 또는 부동 소수점 (예: 1234 또는 99.99)date: 밀리초를 포함하는 13자리 UTC Unix 타임스탬프입니다 (예: 1537399655992).url: 표준 HTTP/HTTPS URL 형식입니다.boolean: 표준 true 또는 false 값입니다.
상담사 표시 여부: 원하는 경우 인간 상담사에게는 표시되지 않지만 라우팅 또는 백엔드 분석에 사용할 수 있는 데이터를 CCAI 플랫폼에 전달할 수 있습니다. 이렇게 하려면 데이터 객체에 "invisible_to_agent": true를 포함하세요.
예약된 CRM 키
CCAI Platform은 사용자를 VIP로 표시하거나 악의적인 행위자에 관해 상담사에게 경고하는 등 플랫폼의 내장 동작을 트리거하는 특정 예약 키를 지원합니다. boolean 유형으로 형식이 지정되어야 하며 보안 JWT 메서드를 사용하여 페이로드에 서명한 경우에만 허용됩니다.
reserved_verified_customer: 고객이 내부 시스템에 의해 인증되었는지 여부를 나타냅니다.reserved_bad_actor: 사용자를 스팸 또는 사기 계정으로 에이전트에게 신고합니다.reserved_repeat_customer: 이 고객이 최근에 지원팀에 자주 문의했는지 여부를 나타냅니다.
예약된 키가 있는 페이로드 예시
{
"iat": 1537399656,
"exp": 1537400256,
"custom_data": {
"reserved_verified_customer": {
"label": "Verified Customer",
"value": true,
"type": "boolean"
},
"reserved_bad_actor": {
"label": "Bad Actor",
"value": false,
"type": "boolean"
},
"reserved_repeat_customer": {
"label": "Repeat Customer",
"value": false,
"type": "boolean"
}
}
}
인증 토큰 관리
SDK는 캐시된 인증 토큰을 수동으로 업데이트하거나 지우는 메서드를 제공합니다.
// Set a new auth token
CCAI.authService?.updateAuthToken("new_auth_token")
// Clear the current token (for example, on user logout)
CCAI.authService?.updateAuthToken(null)
맞춤 데이터 JWT 페이로드 스키마
백엔드에서 Company
Secret Code로 서명할 최종 페이로드를 구성할 때는 이 스키마를 엄격하게 따라야 합니다. 필수 iat(발급 시간) 및 exp (만료) 타임스탬프에 유의하세요.
{
"iat": 1537399656,
"exp": 1537400256,
"custom_data": {
"os_version": {
"label": "OS Version",
"value": "14.0",
"type": "string"
},
"membership_tier": {
"label": "Membership Tier",
"value": "Platinum",
"type": "string"
},
"ssn_last_four": {
"label": "SSN",
"value": "1234",
"type": "string",
"invisible_to_agent": true
},
"reserved_verified_customer": {
"label": "Verified Customer",
"value": true,
"type": "boolean"
}
}
}
푸시 알림 사용 설정
SDK는 다음과 같은 용도로 푸시 알림을 사용합니다.
수신 전화
특정 스마트 작업 및 통화 관련 이벤트
앱이 백그라운드에 있을 때 상태를 유지합니다.
Firebase 설정
Firebase 프로젝트를 만들거나 기존 프로젝트를 사용합니다.
Android 앱을 등록하고
google-services.json파일을 다운로드합니다.google-services.json를 앱 모듈 디렉터리에 배치합니다.루트 수준
build.gradle.kts에서 Google 서비스 플러그인을 추가합니다.// build.gradle.kts (Project) plugins { id("com.google.gms.google-services") version "4.4.2" apply false }앱 수준
build.gradle.kts에서 플러그인을 적용하고Firebase종속 항목을 추가합니다.// app/build.gradle.kts plugins { id("com.google.gms.google-services") } dependencies { // Firebase BoM for version management implementation(platform("com.google.firebase:firebase-bom:33.5.1")) implementation("com.google.firebase:firebase-messaging") }프로젝트를 동기화합니다.
SDK에 FCM 토큰 등록
pushNotificationService를 통해 FCM 푸시 토큰을 등록하고 수신되는 FCM 데이터 페이로드를 SDK로 전달합니다. 앱은 고객 대상 알림이나 통화 중 UI를 렌더링해야 합니다.
onMessageReceived 구현
FCM을 사용하는 경우 푸시 알림을 위해 FirebaseMessagingService 클래스에 리스너를 구현합니다. 이러한 기능이 구현되지 않으면 서비스가 제대로 작동하지 않습니다.
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import com.ccaiplatform.ccaikit.CCAI
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class MyFirebaseMessagingService : FirebaseMessagingService() {
private val scope = CoroutineScope(Dispatchers.IO)
override fun onMessageReceived(remoteMessage: RemoteMessage) {
scope.launch {
// Forward CCAI platform push notifications to the SDK.
CCAI.pushNotificationService?.handlePushNotification(remoteMessage.data)
// Render any customer-facing notification UI in your own app code.
MyNotificationRenderer.showIfNeeded(application, remoteMessage.data)
}
}
}
onNewToken 구현
또한 토큰 업데이트를 처리하는 onNewToken 메서드를 구현합니다. 이렇게 하면 Android용 헤드리스 모바일 SDK가 최신 푸시 알림 토큰을 수신합니다.
class MyFirebaseMessagingService : FirebaseMessagingService() {
// ...
override fun onNewToken(token: String) {
// Fetch the updated token from Firebase and update it in CCAI
CCAI.pushNotificationService?.updatePushToken(token)
}
}
AndroidManifest.xml에 서비스 등록
시스템이 서비스에 푸시 메시지를 전송할 수 있도록 AndroidManifest.xml에 Firebase 메시지 서비스를 추가합니다.
<application>
<service
android:name=".firebase.MyFirebaseMessagingService"
android:exported="true">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
</application>
초기 토큰 등록
앱 시작 시 (SDK 초기화 후) 현재 FCM 토큰을 사전 등록합니다.
import com.google.firebase.messaging.FirebaseMessaging
// In your Application.onCreate(), after CCAI.initialize()
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
if (task.isSuccessful) {
val token = task.result
CCAI.pushNotificationService?.updatePushToken(token)
}
}
알림 권한 처리 (Android 13 이상)
Android 13 (API 수준 33) 이상에서 언급된 앱은 POST_NOTIFICATIONS 런타임 권한을 요청해야 합니다. SDK는 이를 위한 기본 제공 유틸리티를 제공합니다.
import com.ccaiplatform.ccaikit.util.PermissionUtil
// Request notification permissions from the user
PermissionUtil.requestPermissionsForNotifications(activity)
// Check if permissions have been granted
val isGranted = PermissionUtil.isPermissionsForNotificationsGranted(context)
if (isGranted) {
Log.d("CCAI", "Push notifications permitted")
}
푸시 알림이 전송될 수 있도록 FCM 토큰을 등록하기 전에 앱 수명 주기 초기에 (예: 온보딩 또는 첫 번째 실행 중) 이를 호출하세요.