App노자

[Android] RingtoneManager란 본문

Android/AndroidStudio

[Android] RingtoneManager란

앱의노예 2023. 3. 1. 20:07

1. RingtoneManager란

RingtoneManager는 ringtones, notification 및 기타 유형의 효과음들에 대한 접근을 제공한다
RingtoneManager를 이용하는 것으로 현재 설정되어 있는 벨소리를 취득해 울리거나, 벨소리의 목록을 취득하는 것이 가능하다
 
https://developer.android.com/reference/android/media/RingtoneManager

RingtoneManager  |  Android Developers

developer.android.com

 

2. 주요 method

package jp.co.i_bec.happymailapp.service

import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.media.AudioAttributes
import android.media.AudioManager
import android.media.Ringtone
import android.media.RingtoneManager
import android.os.Build
import android.os.Bundle
import android.os.VibrationEffect
import android.os.Vibrator
import android.preference.PreferenceManager
import android.provider.Settings
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.work.ForegroundInfo
import androidx.work.Worker
import androidx.work.WorkerParameters
import androidx.work.impl.utils.futures.SettableFuture
import com.google.common.util.concurrent.ListenableFuture
import com.twilio.voice.CallInvite
import com.twilio.voice.CancelledCallInvite
import jp.co.i_bec.happymailapp.R
import jp.co.i_bec.happymailapp.activity.OutGoingPhoneActivity
import jp.co.i_bec.happymailapp.activity.SplashActivity
import jp.co.i_bec.happymailapp.api.Requestor
import jp.co.i_bec.happymailapp.api.event.APIEvent
import jp.co.i_bec.happymailapp.api.model.BaseModel
import jp.co.i_bec.happymailapp.api.response.ReceivedInviteMessage
import jp.co.i_bec.happymailapp.callback.CommonDialogCallBack
import jp.co.i_bec.happymailapp.common.EventBusHolder
import jp.co.i_bec.happymailapp.constants.AppConstants
import jp.co.i_bec.happymailapp.context.HpmContext
import jp.co.i_bec.happymailapp.eventbus.TwilioPhoneEvent
import jp.co.i_bec.happymailapp.utils.BaseUrlUtils
import jp.co.i_bec.happymailapp.utils.EndPointUrlUtils
import jp.co.i_bec.happymailapp.utils.NotificationUtils
import jp.co.i_bec.happymailapp.utils.ScreenUtils

class IncomingCallNotificationWorker(appContext: Context, workerParams: WorkerParameters):
    Worker(appContext, workerParams) {
    private lateinit var audioManager: AudioManager
    private lateinit var vibrator: Vibrator
    private var ringtoneAlarm: Ringtone? = null

    override fun doWork(): Result {
        audioManager = HpmContext.getInstance().getSystemService(Service.AUDIO_SERVICE) as AudioManager
        vibrator = HpmContext.getInstance().getSystemService(Service.VIBRATOR_SERVICE) as Vibrator

        val action = inputData.getString(AppConstants.KEY_INTENT_ACTION)
        val incomingInvite = HpmContext.getInstance().incomingCallInvite
        val acceptInvite = HpmContext.getInstance().acceptCallInvite
        val rejectInvite = HpmContext.getInstance().rejectCallInvite

        val notificationId = inputData.getInt(AppConstants.INCOMING_CALL_NOTIFICATION_ID, 0)
        val canceledCallInvite = HpmContext.getInstance().cancelledCallInvite

        when(action) {
            AppConstants.ACTION_INCOMING_CALL -> if (incomingInvite != null) {
                BaseUrlUtils.checkBaseUrl(object: CommonDialogCallBack {
                    override fun onResultCallBack(selectNo: Int) {
                        if(selectNo == CommonDialogCallBack.SELECT_OK)
                            handleIncomingCall(incomingInvite!!, notificationId)
                    }
                })
            }

            AppConstants.ACTION_ACCEPT -> if (acceptInvite != null) accept(acceptInvite!!, notificationId) else {
            }
            AppConstants.ACTION_REJECT -> if (rejectInvite != null) reject(rejectInvite!!) else {
            }
            AppConstants.ACTION_CANCEL_CALL -> handleCancelledCall(canceledCallInvite!!)
            else -> {
            }
        }

        return Result.success()
    }

    //android 11未満
    @SuppressLint("RestrictedApi")
    override fun getForegroundInfoAsync(): ListenableFuture<ForegroundInfo> {
        val future = SettableFuture.create<ForegroundInfo>()

        val notificationId = NotificationUtils.CALL_FOREGROUND_NOTIFICATION_ID
        val notification = NotificationUtils.makeCallForegroundServiceNoti(
            HpmContext.getInstance(),
            "",
            NotificationUtils.getChannelId(HpmContext.getInstance())
        )

        future.set(ForegroundInfo(notificationId, notification))
        return future
    }

    private fun getRceivedInviteMessageExtras(response: ReceivedInviteMessage): Bundle {
        val bundle = Bundle()
        bundle.putString(AppConstants.INCOMING_CALL_SENDER_SEQ_ID, response.sender_seq_id)
        bundle.putString(AppConstants.INCOMING_CALL_ROOMNAME, response.room_name)
        bundle.putString(AppConstants.INCOMING_CALL_MEMBER_THUMBNAIL, response.member_thumbnail)
        bundle.putString(AppConstants.INCOMING_CALL_MEMBER_NICKNAME, response.member_nickname)
        bundle.putInt(AppConstants.INCOMING_CALL_MEMBER_SEX, response.member_sex)

        return bundle
    }

    private fun createNotification(callInvite: CallInvite, notificationId: Int, channelImportance: Int, response: ReceivedInviteMessage): Notification {
        val intent = Intent()

        if (HpmContext.getInstance().isAppRunning) {
            // PUSH通知をタップした時、もしパスコードロック状態だったらすぐに表示するため
            HpmContext.getInstance().stopActivityName = OutGoingPhoneActivity::class.java.simpleName
            // Activity Resumeから2度 startActivityされないようにするためstatusを変える
//            HpmContext.getInstance().callState = AppConstants.CallState.VIDEO_INCOMING_START_ACTIVITY
            intent.setClass(HpmContext.getInstance(), OutGoingPhoneActivity::class.java)
        } else {
            // SplashActivityは特別にsetPasscodeCheckNextActivity設定で次の画面でパスコードロック状態だったら出すようにされてるので何もしない
            intent.setClass(HpmContext.getInstance(), SplashActivity::class.java)
        }

        intent.action = AppConstants.ACTION_INCOMING_CALL_NOTIFICATION
        intent.putExtra(AppConstants.KEY_ACTION_TYPE, AppConstants.ACTION_INCOMING_CALL_NOTIFICATION)
        intent.putExtra(AppConstants.INCOMING_CALL_NOTIFICATION_ID, notificationId)
        intent.putExtra(AppConstants.INCOMING_CALL_INVITE, callInvite)
        intent.putExtras(getRceivedInviteMessageExtras(response))
        intent.putExtra(AppConstants.CALL_SID_KEY, callInvite.callSid)
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)

        return NotificationUtils.makeNotification(HpmContext.getInstance(), HpmContext.getInstance().getString(R.string.incoming_voice_call, response.member_nickname),
            intent,
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) createChannel(channelImportance) else NotificationUtils.getChannelId(HpmContext.getInstance()))
    }

    @TargetApi(Build.VERSION_CODES.O)
    private fun createChannel(channelImportance: Int): String {
        var callInviteChannel = NotificationChannel(AppConstants.CALL_CHANNEL_HIGH_IMPORTANCE,
            "Primary Call Channel", NotificationManager.IMPORTANCE_HIGH)
        var channelId = AppConstants.CALL_CHANNEL_HIGH_IMPORTANCE

        if (channelImportance == NotificationManager.IMPORTANCE_LOW) {
            callInviteChannel = NotificationChannel(AppConstants.CALL_CHANNEL_LOW_IMPORTANCE,
                "Primary Call Channel", NotificationManager.IMPORTANCE_LOW)
            channelId = AppConstants.CALL_CHANNEL_LOW_IMPORTANCE
        }

        callInviteChannel.lightColor = Color.GREEN
        callInviteChannel.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
        val notificationManager = HpmContext.getInstance().getSystemService(Service.NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.createNotificationChannel(callInviteChannel)

        return channelId
    }

    private fun accept(callInvite: CallInvite, notificationId: Int) {
        HpmContext.getInstance().callState = AppConstants.CallState.VOICE_CALLING
        stopRinging()

        val bundle = Bundle()
        bundle.putString(AppConstants.KEY_ACTION_TYPE, AppConstants.ACTION_ACCEPT)
        bundle.putParcelable(AppConstants.INCOMING_CALL_INVITE, callInvite)
        bundle.putInt(AppConstants.INCOMING_CALL_NOTIFICATION_ID, notificationId)
        EventBusHolder.EVENT_BUS.post(TwilioPhoneEvent(bundle))
    }

    private fun reject(callInvite: CallInvite) {
        HpmContext.getInstance().callState = AppConstants.CallState.DISCONNECT
        stopRinging()

        callInvite.reject(applicationContext)
    }

    private fun handleCancelledCall(cancelledCallInvite:CancelledCallInvite) {
        HpmContext.getInstance().callState = AppConstants.CallState.DISCONNECT
        stopRinging()

        var bundle = Bundle()
        bundle.putString(AppConstants.KEY_ACTION_TYPE, AppConstants.ACTION_CANCEL_CALL)
        bundle.putParcelable(AppConstants.CANCELLED_CALL_INVITE, cancelledCallInvite)

        EventBusHolder.EVENT_BUS.post(TwilioPhoneEvent(bundle))
    }

    private fun handleIncomingCall(callInvite: CallInvite, notificationId: Int) {
        HpmContext.getInstance().callState = AppConstants.CallState.VOICE_INCOMING
        playRinging()
        ScreenUtils.switchOnScreen(HpmContext.getInstance())

        Requestor.getInstance().received_invite_message(
            EndPointUrlUtils.getEndPointUrl(HpmContext.getInstance().getUrlResponse(), AppConstants.API_RECEIVED_INVITE_MESSAGE),
            HpmContext.getInstance().deviceId,
            PreferenceManager.getDefaultSharedPreferences(HpmContext.getInstance()).getString(AppConstants.IDENTITY, null)
        ).enqueue(object : APIEvent<ReceivedInviteMessage>() {
            override fun onSuccess(response: ReceivedInviteMessage) {
                if (response.is_voice_call == 1) {
                    if (HpmContext.getInstance().checkAppForeGround() && isAppVisible) {
                        sendCallInviteToActivity(callInvite, notificationId, response)
                    } else {
                        setCallInProgressNotification(callInvite, notificationId, response)
                        saveCallInviteToActivity(callInvite, notificationId, response)
                    }
                } else {
                    HpmContext.getInstance().callState = AppConstants.CallState.DISCONNECT
                    stopRinging()
                }
            }

            override fun onFailure(response: BaseModel) {
                HpmContext.getInstance().callState = AppConstants.CallState.DISCONNECT
                stopRinging()
            }
        })
    }

    private fun playRinging() {
        when (audioManager.ringerMode) {
            AudioManager.RINGER_MODE_SILENT -> return
            AudioManager.RINGER_MODE_VIBRATE -> vibrate()
            AudioManager.RINGER_MODE_NORMAL -> {
                audioManager.setStreamVolume(AudioManager.STREAM_RING, audioManager.getStreamVolume(AudioManager.STREAM_RING), AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
                playSound()
            }
        }
    }

    private fun stopRinging() {
        cancelSound()
        cancelVibrator()
    }

    private fun playSound() {
        if (ringtoneAlarm != null) {
            if (ringtoneAlarm!!.isPlaying) {
                ringtoneAlarm!!.stop()
            }
        } else {
            val alarmTone = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM)
            ringtoneAlarm = RingtoneManager.getRingtone(applicationContext, alarmTone)
        }
        ringtoneAlarm?.play()
    }

    private fun cancelSound() {
        ringtoneAlarm?.stop()
    }

    private fun vibrate() {
        if (shouldVibrate()) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                vibrator.vibrate(
                    VibrationEffect.createWaveform(VIBRATE_PATTERN, 0),
                    AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                        .setUsage(AudioAttributes.USAGE_ALARM)
                        .build())

            } else {
                vibrator.vibrate(VIBRATE_PATTERN, 0)
            }
        }
    }

    fun cancelVibrator() {
        vibrator.cancel()
    }

    private fun shouldVibrate(): Boolean {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            shouldVibrateNew()
        } else {
            shouldVibrateOld()
        }
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private fun shouldVibrateNew(): Boolean {
        if (!vibrator.hasVibrator()) {
            return false
        }
        val vibrateWhenRinging = Settings.System.getInt(HpmContext.getInstance().contentResolver, "vibrate_when_ringing", 0) != 0
        val ringerMode = audioManager.ringerMode
        return if (vibrateWhenRinging) {
            ringerMode != AudioManager.RINGER_MODE_SILENT
        } else {
            ringerMode == AudioManager.RINGER_MODE_VIBRATE
        }
    }

    private fun shouldVibrateOld(): Boolean {
        return audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_RINGER)
    }

    @TargetApi(Build.VERSION_CODES.O)
    private fun setCallInProgressNotification(callInvite: CallInvite, notificationId: Int, response: ReceivedInviteMessage) {
        var noti = createNotification(callInvite, notificationId, NotificationManager.IMPORTANCE_HIGH, response)
        val manager = HpmContext.getInstance().getSystemService(Service.NOTIFICATION_SERVICE) as NotificationManager
        manager.notify(notificationId, noti)
    }

    /*
     * Send the CallInvite to the VoiceActivity. Start the activity if it is not running already.
     */
    private fun sendCallInviteToActivity(callInvite: CallInvite, notificationId: Int, response: ReceivedInviteMessage) {
        val intent = callInviteToActivity(callInvite, notificationId, response)
        HpmContext.getInstance().startActivity(intent)
    }

    private fun saveCallInviteToActivity(callInvite: CallInvite, notificationId: Int, response: ReceivedInviteMessage) {
        val intent = callInviteToActivity(callInvite, notificationId, response)
        HpmContext.getInstance().saveIncomingCallIntent(intent)
    }

    private fun callInviteToActivity(callInvite: CallInvite, notificationId: Int, response: ReceivedInviteMessage): Intent {
        val intent = Intent(HpmContext.getInstance(), OutGoingPhoneActivity::class.java)
        intent.action = AppConstants.ACTION_INCOMING_CALL
        intent.putExtra(AppConstants.KEY_ACTION_TYPE, AppConstants.ACTION_INCOMING_CALL)
        intent.putExtra(AppConstants.INCOMING_CALL_NOTIFICATION_ID, notificationId)
        intent.putExtra(AppConstants.INCOMING_CALL_INVITE, callInvite)
        intent.putExtras(getRceivedInviteMessageExtras(response))
        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)

        return intent
    }

    private val isAppVisible: Boolean
        private get() = ProcessLifecycleOwner
            .get()
            .lifecycle
            .currentState
            .isAtLeast(Lifecycle.State.STARTED)

    companion object {
        private val VIBRATE_PATTERN = longArrayOf(0, 1000, 1000)
    }
}
else {
        val alarmTone = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM)
        ringtoneAlarm = RingtoneManager.getRingtone(applicationContext, alarmTone)
    }
    ringtoneAlarm?.play()
}
private fun cancelSound() {

    HpmContext.getInstance().callState = AppConstants.CallState.DISCONNECT
    val alarmTone = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM)
    ringtoneAlarm = RingtoneManager.getRingtone(applicationContext, alarmTone)
    ringtoneAlarm?.stop()
    if (ringtoneAlarm == null) {
        Log.d("Failed to open ringtone " + ringtoneUri)
    }
}

'Android > AndroidStudio' 카테고리의 다른 글

[Android] Preference란  (0) 2023.03.08
[Android] Intent란  (0) 2023.03.05
[Android] AudioManager란  (2) 2023.02.15
[Android] Vibrator란  (0) 2023.02.06
[Android] Context Menu (컨텍스트 메뉴)  (0) 2023.01.22