[Kotlin] FCM과 Badge - 2. Badge
AndroidSharedPreferences Class 생성
SharedPreferences를 간편하게 사용하기 위해 생성
import android.content.Context
import android.content.SharedPreferences
class MyPreferences(context: Context, val name: String) {
private val share: SharedPreferences = context.getSharedPreferences(name, Context.MODE_PRIVATE)
private val shareEdit: SharedPreferences.Editor = share.edit()
fun getValueString(key: String): String {
return share.getString(key, "")
}
fun getValueInt(key: String): Int {
return share.getInt(key, 0)
}
fun getValueBoolean(key: String): Boolean {
return share.getBoolean(key, false)
}
fun setValueString(key: String, value: String) {
shareEdit.putString(key, value)
}
fun setValueInt(key: String, value: Int) {
shareEdit.putInt(key, value)
}
fun setValueBoolean(key: String, value: Boolean) {
shareEdit.putBoolean(key, value)
}
fun editApply() {
shareEdit.apply()
}
}
Badge Class 생성
배지에 대한 모든 설정을 모아둔 클래스
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.util.Log
class Badge(val context: Context) {
private val BADGE = "Badge"
private val TAG = "MyBadge"
private val share = MyPreferences(context, "First")
init {
val data: Boolean = share.getValueBoolean("data")
Log.d(TAG, "badge $data")
if (!data) {
share.setValueBoolean("data", true)
share.editApply()
clearCount()
Log.d(TAG, "badge First")
}
}
// 배지 수
fun getCount(): Int {
return share.getValueInt(BADGE)
}
fun countDown() {
val cnt = getCount() - 1 // 배지 실행
countUpdate(cnt)
}
fun countUp() {
val cnt = getCount() + 1
countUpdate(cnt)
}
// Manifest 에 정의된 메인 activity
private fun getLauncherMainClassName(): String? {
val pm: PackageManager = context.packageManager
val intent = Intent(Intent.ACTION_MAIN)
intent.addCategory(Intent.CATEGORY_LAUNCHER)
val resolveInfo: List<ResolveInfo> = pm.queryIntentActivities(intent, 0)
for (resolve in resolveInfo) {
val packageName: String = resolve.activityInfo.applicationInfo.packageName
if (packageName == context.packageName) {
return resolve.activityInfo.name.toString()
}
}
return null
}
// Launcher Samsung
private fun setBadgeSamsung(count: Int) {
val mainClassName: String = getLauncherMainClassName() ?: return
val intent = Intent("android.intent.action.BADGE_COUNT_UPDATE")
intent.putExtra("badge_count", count)
intent.putExtra("badge_count_package_name", context.packageName) // 앱의 패키지명
intent.putExtra("badge_count_class_name", mainClassName)
context.sendBroadcast(intent)
}
// Launcher Sony
private fun setBadgeSony(count: Int, showFlag: Boolean) {
val mainClassName = getLauncherMainClassName() ?: return
val intent = Intent("com.sonyericsson.home.action.UPDATE_BADGE")
intent.putExtra("com.sonyericsson.home.intent.extra.badge.ACTIVITY_NAME", mainClassName)
intent.putExtra("com.sonyericsson.home.intent.extra.badge.SHOW_MESSAGE", showFlag)
intent.putExtra("com.sonyericsson.home.intent.extra.badge.MESSAGE", count.toString())
intent.putExtra("com.sonyericsson.home.intent.extra.badge.PACKAGE_NAME", context.packageName)
context.sendBroadcast(intent)
}
// 배지 수 갱신
fun countUpdate(count: Int) {
share.setValueInt(BADGE, count)
share.editApply()
setBadgeSamsung(count)
setBadgeSony(count, count != 0)
Log.d(TAG, "countUpdate() : $count")
}
// 배지 초기화
fun clearCount() {
countUpdate(0)
}
// RequestCode 고유값 설정. 연속 알림 시 개별 표시 및 삭제되도록 한다.
fun getUniqueId(): Int {
return (System.currentTimeMillis() / 7).toInt()
}
// 일회용 intent에 Broadcast를 설정하여 Notification Cancel 시 배지 수를 감소시킨다.
fun getCountMinusIntent(): PendingIntent {
val uniId: Int = getUniqueId() + 1
val intent = Intent(context, MyBroadcastReceiver::class.java)
intent.action = context.getString(R.string.badge_count_down)
return PendingIntent.getBroadcast(context, uniId, intent, PendingIntent.FLAG_CANCEL_CURRENT)
}
}
BroadcastReceiver 구현
알림 삭제 시 특정 동작을 실행시키기 위함
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
class MyBroadcastReceiver: BroadcastReceiver() {
val TAG = "MyBroad"
override fun onReceive(context: Context?, intent: Intent?) {
if (intent != null && context != null) {
val name = intent.action
if(name == context.getString(R.string.badge_count_down)) {
Badge(context).countDown()
}
Log.d(TAG, "onReceive() : $name")
}
}
}
알림 설정 추가
class MyFirebaseMessageService: FirebaseMessagingService() {
//...
private fun sendNotification(msgData: Map<String, String>) {
// 배지 수 증가
val badge = Badge(this)
badge.countUp()
// RequestCode, Id를 고유값으로 지정하여 알림이 개별 표시되도록 함
val uniId: Int = badge.getUniqueId()
val intent = Intent(this, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
intent.putExtra("title", getString(R.string.badge_count_down)) // 알림 실행 시 배지 감소할 수 있도록 값 전달
val pendingIntent = PendingIntent.getActivity(this, uniId, intent, PendingIntent.FLAG_ONE_SHOT)
val channelId = getString(R.string.fcm_channel_id)
val soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val title = msgData.getValue("title")
val message = msgData.getValue("msg")
val notificationBuilder = NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.ic_notify)
.setContentTitle(title)
.setContentText(message)
.setAutoCancel(true)
.setSound(soundUri)
.setContentIntent(pendingIntent)
.setStyle(NotificationCompat.BigTextStyle().setBigContentTitle(title).bigText(message)) // 상세보기
.setDeleteIntent(badge.getCountMinusIntent()) // 알림 삭제 시 Intent
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(channelId, "Notice", NotificationManager.IMPORTANCE_DEFAULT)
notificationManager.createNotificationChannel(channel)
}
notificationManager.notify(uniId, notificationBuilder.build())
Log.d(TAG, "sendNotification()")
}
}
Manifest 설정
<manifest ... >
<!-- Badge Sony -->
<uses-permission android:name="com.sonyericsson.home.permission.BROADCAST_BADGE"/>
<application
<!-- Broadcast Receiver -->
<receiver android:name=".modul.MyBroadcastReceiver">
<intent-filter>
<action android:name="Notification_Cancelled_Badge_Count_Down"/>
</intent-filter>
</receiver>
</application>
</manifest>
MainActivity에 intent 설정
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import ngc.user.promessa.lovefield.badgefcm.modul.Badge
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
intentExtra(intent)
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
intentExtra(intent)
}
fun intentExtra(intent: Intent?) {
if (intent != null && intent.extras != null) {
val title: String? = intent.extras.getString("title")
if (title == getString(R.string.badge_count_down)) {
Badge(this).countDown()
}
}
}
}
로그
- 앱 첫 실행
D/DICO: Badge Count : 0
E/DICO: onCreate
E/DICO: onResume
E/DICO: onResume
- 알림 삭제
D/DICO: Badge Count : 1
D/DICO: FCM onMessageReceived 1 {title=알림이 도착했습니다., message=lovefield}
D/DICO: Badge Count : 0
- 앱 종료 후 알림 확인
D/DICO: Badge Count : 1
D/DICO: FCM onMessageReceived 1 {title=알림이 도착했습니다., message=Devellany}
D/DICO: Badge Count : 0
E/DICO: onCreate
E/DICO: onResume
E/DICO: onResume
- 앱 화면 열린 상태에서 알림 확인
D/DICO: Badge Count : 1
D/DICO: FCM onMessageReceived 1 {title=알림이 도착했습니다., message=아라링}
D/DICO: Badge Count : 0
E/DICO: onNewIntent
E/DICO: onResume
- 앱 내려 둔 상태에서 알림 확인
D/DICO: Badge Count : 1
D/DICO: FCM onMessageReceived 1 {title=알림이 도착했습니다., message=민갤}
D/DICO: Badge Count : 0
E/DICO: onCreate
E/DICO: onResume
E/DICO: onResume