[Kotlin] FCM과 Badge - 2. Badge

Android

(Update : 2019-12-25)

Language :

SharedPreferences 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

민갤

Back-End Developer

백엔드 개발자입니다.