{"id":1889,"date":"2023-06-06T22:57:51","date_gmt":"2023-06-06T13:57:51","guid":{"rendered":"http:\/\/batmask.dothome.co.kr\/?p=1889"},"modified":"2025-09-11T18:53:58","modified_gmt":"2025-09-11T09:53:58","slug":"deskclock%ec%9d%98-timer-expired%ec%8b%9c-%ea%b5%ac%ed%98%84-%eb%b6%84%ec%84%9d","status":"publish","type":"post","link":"http:\/\/batmask.net\/index.php\/2023\/06\/06\/1889\/","title":{"rendered":"DeskClock \ucf54\ub4dc\ubd84\uc11d #1 : \uc758 Timer expired\uc2dc \uad6c\ud604 \ubd84\uc11d"},"content":{"rendered":"\n<p>\ud0c0\uc774\uba38\ub97c \ub9cc\ub4e4\ub2e4\uac00, \uc548\ub4dc\ub85c\uc774\ub4dc\uc758 \uc624\ud508\uc18c\uc2a4 \uc571\uc778 DeskClock \uc18c\uc2a4\ub97c \uc880 \uc0b4\ud3b4\ubd24\ub2e4.<\/p>\n\n\n\n<p> \ud0c0\uc774\uba38 \ub3d9\uc791\uc2dc, AlarmManager\uc5d0 \uc644\ub8cc\uc2dc\uac04\uc744 \ub4f1\ub85d\ud55c\ub2e4. \uc2dc\uac04 \ubcc0\uacbd\uc2dc, AlarmManager\uc5d0 \ub4f1\ub85d\ud55c \uc54c\ub78c\uc744 \uc5c5\ub370\uc774\ud2b8 \uc2dc\ud0a8\ub2e4.<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-Roboto-Mono.ttf\" style=\"font-size:clamp(14px, .875rem, 21px);font-family:Code-Pro-Roboto-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#adbac7;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:clamp(20px, 1.25rem, 30px);--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#22272e\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#adbac7;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>@JvmStatic\nfun createTimerExpiredIntent(context: Context, timer: Timer?): Intent {\n    val timerId = timer?.id ?: -1\n    return Intent(context, TimerService::class.java)\n        .setAction(ACTION_TIMER_EXPIRED)\n        .putExtra(EXTRA_TIMER_ID, timerId)\n}<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki github-dark-dimmed\" style=\"background-color: #22272e\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #F69D50\">@JvmStatic<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F47067\">fun<\/span><span style=\"color: #ADBAC7\"> <\/span><span style=\"color: #DCBDFB\">createTimerExpiredIntent<\/span><span style=\"color: #ADBAC7\">(context: <\/span><span style=\"color: #F69D50\">Context<\/span><span style=\"color: #ADBAC7\">, timer: <\/span><span style=\"color: #F69D50\">Timer<\/span><span style=\"color: #ADBAC7\">?): <\/span><span style=\"color: #F69D50\">Intent<\/span><span style=\"color: #ADBAC7\"> {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    <\/span><span style=\"color: #F47067\">val<\/span><span style=\"color: #ADBAC7\"> timerId <\/span><span style=\"color: #F47067\">=<\/span><span style=\"color: #ADBAC7\"> timer?.id ?: <\/span><span style=\"color: #F47067\">-<\/span><span style=\"color: #6CB6FF\">1<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    <\/span><span style=\"color: #F47067\">return<\/span><span style=\"color: #ADBAC7\"> <\/span><span style=\"color: #DCBDFB\">Intent<\/span><span style=\"color: #ADBAC7\">(context, TimerService::<\/span><span style=\"color: #DCBDFB\">class<\/span><span style=\"color: #ADBAC7\">.java)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">        .<\/span><span style=\"color: #DCBDFB\">setAction<\/span><span style=\"color: #ADBAC7\">(ACTION_TIMER_EXPIRED)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">        .<\/span><span style=\"color: #DCBDFB\">putExtra<\/span><span style=\"color: #ADBAC7\">(EXTRA_TIMER_ID, timerId)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">}<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-Roboto-Mono.ttf\" style=\"font-size:clamp(14px, .875rem, 21px);font-family:Code-Pro-Roboto-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#adbac7;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:clamp(20px, 1.25rem, 30px);--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#22272e\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#adbac7;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>private fun updateAlarmManager() {\n    \/\/ Locate the next firing timer if one exists.\n    var nextExpiringTimer: Timer? = null\n    for (timer in mutableTimers) {\n        if (timer.isRunning) {\n            if (nextExpiringTimer == null) {\n                nextExpiringTimer = timer\n            } else if (timer.expirationTime &lt; nextExpiringTimer.expirationTime) {\n                nextExpiringTimer = timer\n            }\n        }\n    }\n\n    \/\/ Build the intent that signals the timer expiration.\n    val intent: Intent = TimerService.createTimerExpiredIntent(mContext, nextExpiringTimer)\n    if (nextExpiringTimer == null) {\n        \/\/ Cancel the existing timer expiration callback.\n        val pi: PendingIntent? = PendingIntent.getService(mContext,\n                0, intent, PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_NO_CREATE)\n        if (pi != null) {\n            mAlarmManager.cancel(pi)\n            pi.cancel()\n        }\n    } else {\n        \/\/ Update the existing timer expiration callback.\n        val pi: PendingIntent = PendingIntent.getService(mContext,\n                0, intent, PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_UPDATE_CURRENT)\n        schedulePendingIntent(mAlarmManager, nextExpiringTimer.expirationTime, pi)\n    }\n}<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki github-dark-dimmed\" style=\"background-color: #22272e\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #F47067\">private<\/span><span style=\"color: #ADBAC7\"> <\/span><span style=\"color: #F47067\">fun<\/span><span style=\"color: #ADBAC7\"> <\/span><span style=\"color: #DCBDFB\">updateAlarmManager<\/span><span style=\"color: #ADBAC7\">() {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    <\/span><span style=\"color: #768390\">\/\/ Locate the next firing timer if one exists.<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    <\/span><span style=\"color: #F47067\">var<\/span><span style=\"color: #ADBAC7\"> nextExpiringTimer: <\/span><span style=\"color: #F69D50\">Timer<\/span><span style=\"color: #ADBAC7\">? <\/span><span style=\"color: #F47067\">=<\/span><span style=\"color: #ADBAC7\"> <\/span><span style=\"color: #6CB6FF\">null<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    <\/span><span style=\"color: #F47067\">for<\/span><span style=\"color: #ADBAC7\"> (timer <\/span><span style=\"color: #F47067\">in<\/span><span style=\"color: #ADBAC7\"> mutableTimers) {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">        <\/span><span style=\"color: #F47067\">if<\/span><span style=\"color: #ADBAC7\"> (timer.isRunning) {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">            <\/span><span style=\"color: #F47067\">if<\/span><span style=\"color: #ADBAC7\"> (nextExpiringTimer <\/span><span style=\"color: #F47067\">==<\/span><span style=\"color: #ADBAC7\"> <\/span><span style=\"color: #6CB6FF\">null<\/span><span style=\"color: #ADBAC7\">) {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">                nextExpiringTimer <\/span><span style=\"color: #F47067\">=<\/span><span style=\"color: #ADBAC7\"> timer<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">            } <\/span><span style=\"color: #F47067\">else<\/span><span style=\"color: #ADBAC7\"> <\/span><span style=\"color: #F47067\">if<\/span><span style=\"color: #ADBAC7\"> (timer.expirationTime <\/span><span style=\"color: #F47067\">&lt;<\/span><span style=\"color: #ADBAC7\"> nextExpiringTimer.expirationTime) {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">                nextExpiringTimer <\/span><span style=\"color: #F47067\">=<\/span><span style=\"color: #ADBAC7\"> timer<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">            }<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">        }<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    }<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    <\/span><span style=\"color: #768390\">\/\/ Build the intent that signals the timer expiration.<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    <\/span><span style=\"color: #F47067\">val<\/span><span style=\"color: #ADBAC7\"> intent: <\/span><span style=\"color: #F69D50\">Intent<\/span><span style=\"color: #ADBAC7\"> <\/span><span style=\"color: #F47067\">=<\/span><span style=\"color: #ADBAC7\"> TimerService.<\/span><span style=\"color: #DCBDFB\">createTimerExpiredIntent<\/span><span style=\"color: #ADBAC7\">(mContext, nextExpiringTimer)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    <\/span><span style=\"color: #F47067\">if<\/span><span style=\"color: #ADBAC7\"> (nextExpiringTimer <\/span><span style=\"color: #F47067\">==<\/span><span style=\"color: #ADBAC7\"> <\/span><span style=\"color: #6CB6FF\">null<\/span><span style=\"color: #ADBAC7\">) {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">        <\/span><span style=\"color: #768390\">\/\/ Cancel the existing timer expiration callback.<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">        <\/span><span style=\"color: #F47067\">val<\/span><span style=\"color: #ADBAC7\"> pi: <\/span><span style=\"color: #F69D50\">PendingIntent<\/span><span style=\"color: #ADBAC7\">? <\/span><span style=\"color: #F47067\">=<\/span><span style=\"color: #ADBAC7\"> PendingIntent.<\/span><span style=\"color: #DCBDFB\">getService<\/span><span style=\"color: #ADBAC7\">(mContext,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">                <\/span><span style=\"color: #6CB6FF\">0<\/span><span style=\"color: #ADBAC7\">, intent, PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_NO_CREATE)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">        <\/span><span style=\"color: #F47067\">if<\/span><span style=\"color: #ADBAC7\"> (pi <\/span><span style=\"color: #F47067\">!=<\/span><span style=\"color: #ADBAC7\"> <\/span><span style=\"color: #6CB6FF\">null<\/span><span style=\"color: #ADBAC7\">) {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">            mAlarmManager.<\/span><span style=\"color: #DCBDFB\">cancel<\/span><span style=\"color: #ADBAC7\">(pi)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">            pi.<\/span><span style=\"color: #DCBDFB\">cancel<\/span><span style=\"color: #ADBAC7\">()<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">        }<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    } <\/span><span style=\"color: #F47067\">else<\/span><span style=\"color: #ADBAC7\"> {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">        <\/span><span style=\"color: #768390\">\/\/ Update the existing timer expiration callback.<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">        <\/span><span style=\"color: #F47067\">val<\/span><span style=\"color: #ADBAC7\"> pi: <\/span><span style=\"color: #F69D50\">PendingIntent<\/span><span style=\"color: #ADBAC7\"> <\/span><span style=\"color: #F47067\">=<\/span><span style=\"color: #ADBAC7\"> PendingIntent.<\/span><span style=\"color: #DCBDFB\">getService<\/span><span style=\"color: #ADBAC7\">(mContext,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">                <\/span><span style=\"color: #6CB6FF\">0<\/span><span style=\"color: #ADBAC7\">, intent, PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_UPDATE_CURRENT)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">        <\/span><span style=\"color: #DCBDFB\">schedulePendingIntent<\/span><span style=\"color: #ADBAC7\">(mAlarmManager, nextExpiringTimer.expirationTime, pi)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    }<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">}<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p>TimerService\uac00 \uc2dc\uc791\ub418\uba74, onStartCommand()\uc5d0\uc11c \ub2e4\uc74c\uacfc\uac19\uc774 expireTimer()\ub97c \ubd88\ub7ec\uc900\ub2e4.<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-Roboto-Mono.ttf\" style=\"font-size:clamp(14px, .875rem, 21px);font-family:Code-Pro-Roboto-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#adbac7;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:clamp(20px, 1.25rem, 30px);--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#22272e\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#adbac7;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>...\nACTION_TIMER_EXPIRED -> {\n    Events.sendTimerEvent(R.string.action_fire, label)\n    DataModel.dataModel.expireTimer(this, timer)\n}\n...<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki github-dark-dimmed\" style=\"background-color: #22272e\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #F47067\">..<\/span><span style=\"color: #ADBAC7\">.<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">ACTION_TIMER_EXPIRED <\/span><span style=\"color: #F47067\">-&gt;<\/span><span style=\"color: #ADBAC7\"> {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    Events.<\/span><span style=\"color: #DCBDFB\">sendTimerEvent<\/span><span style=\"color: #ADBAC7\">(R.string.action_fire, label)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    DataModel.dataModel.<\/span><span style=\"color: #DCBDFB\">expireTimer<\/span><span style=\"color: #ADBAC7\">(<\/span><span style=\"color: #6CB6FF\">this<\/span><span style=\"color: #ADBAC7\">, timer)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">}<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F47067\">..<\/span><span style=\"color: #ADBAC7\">.<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p>TimerModel\uc758 expreTimer()\uac00 \ud638\ucd9c\ub418\uace0 \uc2e4\ud589\uc911\uc778 \uc11c\ube44\uc2a4\uac00 \uc800\uc7a5\ub41c\uac8c \uc5c6\ub2e4\uba74, \ub118\uaca8\ubc1b\uc740 \uc11c\ube44\uc2a4\ub97c \uc2e4\ud589\uc911 \uc11c\ube44\uc2a4\ub85c \uc124\uc815\ud55c\ub2e4. \uadf8\ub9ac\uace0 updateTimer()\ub97c \ubd88\ub7ec\uc900\ub2e4.<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-Roboto-Mono.ttf\" style=\"font-size:clamp(14px, .875rem, 21px);font-family:Code-Pro-Roboto-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#adbac7;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:clamp(20px, 1.25rem, 30px);--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#22272e\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#adbac7;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>fun expireTimer(service: Service?, timer: Timer) {\n    if (mService == null) {\n        \/\/ If this is the first expired timer, retain the service that will be used to start\n        \/\/ the heads-up notification in the foreground.\n        mService = service\n    } else if (mService != service) {\n        \/\/ If this is not the first expired timer, the service should match the one given when\n        \/\/ the first timer expired.\n        LogUtils.wtf(\"Expected TimerServices to be identical\")\n    }\n\n    updateTimer(timer.expire())\n}<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki github-dark-dimmed\" style=\"background-color: #22272e\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #F47067\">fun<\/span><span style=\"color: #ADBAC7\"> <\/span><span style=\"color: #DCBDFB\">expireTimer<\/span><span style=\"color: #ADBAC7\">(service: <\/span><span style=\"color: #F69D50\">Service<\/span><span style=\"color: #ADBAC7\">?, timer: <\/span><span style=\"color: #F69D50\">Timer<\/span><span style=\"color: #ADBAC7\">) {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    <\/span><span style=\"color: #F47067\">if<\/span><span style=\"color: #ADBAC7\"> (mService <\/span><span style=\"color: #F47067\">==<\/span><span style=\"color: #ADBAC7\"> <\/span><span style=\"color: #6CB6FF\">null<\/span><span style=\"color: #ADBAC7\">) {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">        <\/span><span style=\"color: #768390\">\/\/ If this is the first expired timer, retain the service that will be used to start<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">        <\/span><span style=\"color: #768390\">\/\/ the heads-up notification in the foreground.<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">        mService <\/span><span style=\"color: #F47067\">=<\/span><span style=\"color: #ADBAC7\"> service<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    } <\/span><span style=\"color: #F47067\">else<\/span><span style=\"color: #ADBAC7\"> <\/span><span style=\"color: #F47067\">if<\/span><span style=\"color: #ADBAC7\"> (mService <\/span><span style=\"color: #F47067\">!=<\/span><span style=\"color: #ADBAC7\"> service) {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">        <\/span><span style=\"color: #768390\">\/\/ If this is not the first expired timer, the service should match the one given when<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">        <\/span><span style=\"color: #768390\">\/\/ the first timer expired.<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">        LogUtils.<\/span><span style=\"color: #DCBDFB\">wtf<\/span><span style=\"color: #ADBAC7\">(<\/span><span style=\"color: #96D0FF\">&quot;Expected TimerServices to be identical&quot;<\/span><span style=\"color: #ADBAC7\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    }<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    <\/span><span style=\"color: #DCBDFB\">updateTimer<\/span><span style=\"color: #ADBAC7\">(timer.<\/span><span style=\"color: #DCBDFB\">expire<\/span><span style=\"color: #ADBAC7\">())<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">}<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p>updateTimer()\ub97c \ubcf4\uba74, \ud0c0\uc774\uba38\uac00 expired\ub410\uc744 \ub54c, updateHeadsUpNotification()\uc744 \ubd88\ub7ec\uc900\ub2e4.<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-Roboto-Mono.ttf\" style=\"font-size:clamp(14px, .875rem, 21px);font-family:Code-Pro-Roboto-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#adbac7;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:clamp(20px, 1.25rem, 30px);--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#22272e\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#adbac7;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>fun updateTimer(timer: Timer) {\n    val before = doUpdateTimer(timer)\n\n    \/\/ Update the notification after updating the timer data.\n    updateNotification()\n\n    \/\/ If the timer started or stopped being expired, update the heads-up notification.\n    if (before.state != timer.state) {\n        if (before.isExpired || timer.isExpired) {\n            updateHeadsUpNotification()\n        }\n    }\n}<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki github-dark-dimmed\" style=\"background-color: #22272e\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #F47067\">fun<\/span><span style=\"color: #ADBAC7\"> <\/span><span style=\"color: #DCBDFB\">updateTimer<\/span><span style=\"color: #ADBAC7\">(timer: <\/span><span style=\"color: #F69D50\">Timer<\/span><span style=\"color: #ADBAC7\">) {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    <\/span><span style=\"color: #F47067\">val<\/span><span style=\"color: #ADBAC7\"> before <\/span><span style=\"color: #F47067\">=<\/span><span style=\"color: #ADBAC7\"> <\/span><span style=\"color: #DCBDFB\">doUpdateTimer<\/span><span style=\"color: #ADBAC7\">(timer)<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    <\/span><span style=\"color: #768390\">\/\/ Update the notification after updating the timer data.<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    <\/span><span style=\"color: #DCBDFB\">updateNotification<\/span><span style=\"color: #ADBAC7\">()<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    <\/span><span style=\"color: #768390\">\/\/ If the timer started or stopped being expired, update the heads-up notification.<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    <\/span><span style=\"color: #F47067\">if<\/span><span style=\"color: #ADBAC7\"> (before.state <\/span><span style=\"color: #F47067\">!=<\/span><span style=\"color: #ADBAC7\"> timer.state) {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">        <\/span><span style=\"color: #F47067\">if<\/span><span style=\"color: #ADBAC7\"> (before.isExpired <\/span><span style=\"color: #F47067\">||<\/span><span style=\"color: #ADBAC7\"> timer.isExpired) {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">            <\/span><span style=\"color: #DCBDFB\">updateHeadsUpNotification<\/span><span style=\"color: #ADBAC7\">()<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">        }<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    }<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">}<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p>updateHeadsUpNotification()\uc5d0\uc11c\ub294 \uc11c\ube44\uc2a4\uc758 \uc720\ubb34\uc640 expired\ub41c \ud0c0\uc774\uba38\uc758 \uc720\ubb34\ub97c \uccb4\ud06c\ud558\uace0 TimerNotificationBuilder\uc758 buildHeadsUp()\uc73c\ub85c \uc54c\ub78c\uc6a9 Notification\uc744 \ub9cc\ub4e0\ub2e4. \uadf8\ub9ac\uace0 service\uc758 setForeground()\ub85c foreground service\ub85c Notification\uc744 \ud45c\uc2dc\ud55c\ub2e4.<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-Roboto-Mono.ttf\" style=\"font-size:clamp(14px, .875rem, 21px);font-family:Code-Pro-Roboto-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#adbac7;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:clamp(20px, 1.25rem, 30px);--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#22272e\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#adbac7;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>private fun updateHeadsUpNotification() {\n    \/\/ Nothing can be done with the heads-up notification without a valid service reference.\n    if (mService == null) {\n        return\n    }\n\n    val expired = expiredTimers\n\n    \/\/ If no expired timers exist, stop the service (which cancels the foreground notification).\n    if (expired.isEmpty()) {\n        mService!!.stopSelf()\n        mService = null\n        return\n    }\n\n    \/\/ Otherwise build and post a foreground notification reflecting the latest expired timers.\n    val notification: Notification = mNotificationBuilder.buildHeadsUp(mContext, expired)\n    val notificationId = mNotificationModel.expiredTimerNotificationId\n    mService!!.startForeground(notificationId, notification)\n}<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki github-dark-dimmed\" style=\"background-color: #22272e\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #F47067\">private<\/span><span style=\"color: #ADBAC7\"> <\/span><span style=\"color: #F47067\">fun<\/span><span style=\"color: #ADBAC7\"> <\/span><span style=\"color: #DCBDFB\">updateHeadsUpNotification<\/span><span style=\"color: #ADBAC7\">() {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    <\/span><span style=\"color: #768390\">\/\/ Nothing can be done with the heads-up notification without a valid service reference.<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    <\/span><span style=\"color: #F47067\">if<\/span><span style=\"color: #ADBAC7\"> (mService <\/span><span style=\"color: #F47067\">==<\/span><span style=\"color: #ADBAC7\"> <\/span><span style=\"color: #6CB6FF\">null<\/span><span style=\"color: #ADBAC7\">) {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">        <\/span><span style=\"color: #F47067\">return<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    }<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    <\/span><span style=\"color: #F47067\">val<\/span><span style=\"color: #ADBAC7\"> expired <\/span><span style=\"color: #F47067\">=<\/span><span style=\"color: #ADBAC7\"> expiredTimers<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    <\/span><span style=\"color: #768390\">\/\/ If no expired timers exist, stop the service (which cancels the foreground notification).<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    <\/span><span style=\"color: #F47067\">if<\/span><span style=\"color: #ADBAC7\"> (expired.<\/span><span style=\"color: #DCBDFB\">isEmpty<\/span><span style=\"color: #ADBAC7\">()) {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">        mService<\/span><span style=\"color: #F47067\">!!<\/span><span style=\"color: #ADBAC7\">.<\/span><span style=\"color: #DCBDFB\">stopSelf<\/span><span style=\"color: #ADBAC7\">()<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">        mService <\/span><span style=\"color: #F47067\">=<\/span><span style=\"color: #ADBAC7\"> <\/span><span style=\"color: #6CB6FF\">null<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">        <\/span><span style=\"color: #F47067\">return<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    }<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    <\/span><span style=\"color: #768390\">\/\/ Otherwise build and post a foreground notification reflecting the latest expired timers.<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    <\/span><span style=\"color: #F47067\">val<\/span><span style=\"color: #ADBAC7\"> notification: <\/span><span style=\"color: #F69D50\">Notification<\/span><span style=\"color: #ADBAC7\"> <\/span><span style=\"color: #F47067\">=<\/span><span style=\"color: #ADBAC7\"> mNotificationBuilder.<\/span><span style=\"color: #DCBDFB\">buildHeadsUp<\/span><span style=\"color: #ADBAC7\">(mContext, expired)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    <\/span><span style=\"color: #F47067\">val<\/span><span style=\"color: #ADBAC7\"> notificationId <\/span><span style=\"color: #F47067\">=<\/span><span style=\"color: #ADBAC7\"> mNotificationModel.expiredTimerNotificationId<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    mService<\/span><span style=\"color: #F47067\">!!<\/span><span style=\"color: #ADBAC7\">.<\/span><span style=\"color: #DCBDFB\">startForeground<\/span><span style=\"color: #ADBAC7\">(notificationId, notification)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">}<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p>expired\ub41c \ud0c0\uc774\uba38 \uc54c\ub78c\uc6a9 Notification\uc740 TimerNotificationBuilder\uc5d0\uc11c \ub2e4\uc74c\uacfc \uac19\uc774 \ub9cc\ub4e4\uc5b4 \uc8fc\uace0 \uc788\ub2e4.<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-Roboto-Mono.ttf\" style=\"font-size:clamp(14px, .875rem, 21px);font-family:Code-Pro-Roboto-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#adbac7;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:clamp(20px, 1.25rem, 30px);--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#22272e\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#adbac7;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>\/\/ Content intent shows the timer full screen when clicked.\nval content = Intent(context, ExpiredTimersActivity::class.java)\nval contentIntent: PendingIntent = Utils.pendingActivityIntent(context, content)\n\n\/\/ Full screen intent has flags so it is different than the content intent.\nval fullScreen: Intent = Intent(context, ExpiredTimersActivity::class.java)\n                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NO_USER_ACTION)\nval pendingFullScreen: PendingIntent = Utils.pendingActivityIntent(context, fullScreen)\n\nval notification: Builder = Builder(\ncontext, TIMER_MODEL_NOTIFICATION_CHANNEL_ID)\n    .setOngoing(true)\n    .setLocalOnly(true)\n    .setShowWhen(false)\n    .setAutoCancel(false)\n    .setContentIntent(contentIntent)\n    .setPriority(NotificationManager.IMPORTANCE_HIGH)\n    .setDefaults(Notification.DEFAULT_LIGHTS)\n    .setSmallIcon(R.drawable.stat_notify_timer)\n    .setFullScreenIntent(pendingFullScreen, true)\n    .setStyle(NotificationCompat.DecoratedCustomViewStyle())\n    .setColor(ContextCompat.getColor(context, R.color.default_background))<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki github-dark-dimmed\" style=\"background-color: #22272e\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #768390\">\/\/ Content intent shows the timer full screen when clicked.<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F47067\">val<\/span><span style=\"color: #ADBAC7\"> content <\/span><span style=\"color: #F47067\">=<\/span><span style=\"color: #ADBAC7\"> <\/span><span style=\"color: #DCBDFB\">Intent<\/span><span style=\"color: #ADBAC7\">(context, ExpiredTimersActivity::<\/span><span style=\"color: #DCBDFB\">class<\/span><span style=\"color: #ADBAC7\">.java)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F47067\">val<\/span><span style=\"color: #ADBAC7\"> contentIntent: <\/span><span style=\"color: #F69D50\">PendingIntent<\/span><span style=\"color: #ADBAC7\"> <\/span><span style=\"color: #F47067\">=<\/span><span style=\"color: #ADBAC7\"> Utils.<\/span><span style=\"color: #DCBDFB\">pendingActivityIntent<\/span><span style=\"color: #ADBAC7\">(context, content)<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #768390\">\/\/ Full screen intent has flags so it is different than the content intent.<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F47067\">val<\/span><span style=\"color: #ADBAC7\"> fullScreen: <\/span><span style=\"color: #F69D50\">Intent<\/span><span style=\"color: #ADBAC7\"> <\/span><span style=\"color: #F47067\">=<\/span><span style=\"color: #ADBAC7\"> <\/span><span style=\"color: #DCBDFB\">Intent<\/span><span style=\"color: #ADBAC7\">(context, ExpiredTimersActivity::<\/span><span style=\"color: #DCBDFB\">class<\/span><span style=\"color: #ADBAC7\">.java)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">                .<\/span><span style=\"color: #DCBDFB\">setFlags<\/span><span style=\"color: #ADBAC7\">(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NO_USER_ACTION)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F47067\">val<\/span><span style=\"color: #ADBAC7\"> pendingFullScreen: <\/span><span style=\"color: #F69D50\">PendingIntent<\/span><span style=\"color: #ADBAC7\"> <\/span><span style=\"color: #F47067\">=<\/span><span style=\"color: #ADBAC7\"> Utils.<\/span><span style=\"color: #DCBDFB\">pendingActivityIntent<\/span><span style=\"color: #ADBAC7\">(context, fullScreen)<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F47067\">val<\/span><span style=\"color: #ADBAC7\"> notification: <\/span><span style=\"color: #F69D50\">Builder<\/span><span style=\"color: #ADBAC7\"> <\/span><span style=\"color: #F47067\">=<\/span><span style=\"color: #ADBAC7\"> <\/span><span style=\"color: #DCBDFB\">Builder<\/span><span style=\"color: #ADBAC7\">(<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">context, TIMER_MODEL_NOTIFICATION_CHANNEL_ID)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    .<\/span><span style=\"color: #DCBDFB\">setOngoing<\/span><span style=\"color: #ADBAC7\">(<\/span><span style=\"color: #6CB6FF\">true<\/span><span style=\"color: #ADBAC7\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    .<\/span><span style=\"color: #DCBDFB\">setLocalOnly<\/span><span style=\"color: #ADBAC7\">(<\/span><span style=\"color: #6CB6FF\">true<\/span><span style=\"color: #ADBAC7\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    .<\/span><span style=\"color: #DCBDFB\">setShowWhen<\/span><span style=\"color: #ADBAC7\">(<\/span><span style=\"color: #6CB6FF\">false<\/span><span style=\"color: #ADBAC7\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    .<\/span><span style=\"color: #DCBDFB\">setAutoCancel<\/span><span style=\"color: #ADBAC7\">(<\/span><span style=\"color: #6CB6FF\">false<\/span><span style=\"color: #ADBAC7\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    .<\/span><span style=\"color: #DCBDFB\">setContentIntent<\/span><span style=\"color: #ADBAC7\">(contentIntent)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    .<\/span><span style=\"color: #DCBDFB\">setPriority<\/span><span style=\"color: #ADBAC7\">(NotificationManager.IMPORTANCE_HIGH)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    .<\/span><span style=\"color: #DCBDFB\">setDefaults<\/span><span style=\"color: #ADBAC7\">(Notification.DEFAULT_LIGHTS)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    .<\/span><span style=\"color: #DCBDFB\">setSmallIcon<\/span><span style=\"color: #ADBAC7\">(R.drawable.stat_notify_timer)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    .<\/span><span style=\"color: #DCBDFB\">setFullScreenIntent<\/span><span style=\"color: #ADBAC7\">(pendingFullScreen, <\/span><span style=\"color: #6CB6FF\">true<\/span><span style=\"color: #ADBAC7\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    .<\/span><span style=\"color: #DCBDFB\">setStyle<\/span><span style=\"color: #ADBAC7\">(NotificationCompat.<\/span><span style=\"color: #DCBDFB\">DecoratedCustomViewStyle<\/span><span style=\"color: #ADBAC7\">())<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    .<\/span><span style=\"color: #DCBDFB\">setColor<\/span><span style=\"color: #ADBAC7\">(ContextCompat.<\/span><span style=\"color: #DCBDFB\">getColor<\/span><span style=\"color: #ADBAC7\">(context, R.color.default_background))<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p> \uc644\ub8cc\ub41c \ud0c0\uc774\uba38\ub97c \ubcf4\uc5ec\uc8fc\uae30 \uc704\ud55c ExpiredTimersActivity\ub97c \ud654\uba74\uc5d0 \ub744\uc6b0\uae30 \uc704\ud574 \ub2e4\uc74c\uacfc \uac19\uc774 \ucc98\ub9ac\ud574\uc8fc\uace0 \uc788\ub2e4. <\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-Roboto-Mono.ttf\" style=\"font-size:clamp(14px, .875rem, 21px);font-family:Code-Pro-Roboto-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#adbac7;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:clamp(20px, 1.25rem, 30px);--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#22272e\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#adbac7;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>view.systemUiVisibility = View.SYSTEM_UI_FLAG_LOW_PROFILE\n\ngetWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON\n    or WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON)\n\nsetTurnScreenOn(true)\nsetShowWhenLocked(true)\n\n\/\/ Close dialogs and window shade, so this is fully visible\nsendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))\n\n\/\/ Honor rotation on tablets; fix the orientation on phones.\nif (!getResources().getBoolean(R.bool.rotateAlarmAlert)) {\n    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR)\n}<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki github-dark-dimmed\" style=\"background-color: #22272e\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #ADBAC7\">view.systemUiVisibility <\/span><span style=\"color: #F47067\">=<\/span><span style=\"color: #ADBAC7\"> View.SYSTEM_UI_FLAG_LOW_PROFILE<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #DCBDFB\">getWindow<\/span><span style=\"color: #ADBAC7\">().<\/span><span style=\"color: #DCBDFB\">addFlags<\/span><span style=\"color: #ADBAC7\">(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    or WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON)<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #DCBDFB\">setTurnScreenOn<\/span><span style=\"color: #ADBAC7\">(<\/span><span style=\"color: #6CB6FF\">true<\/span><span style=\"color: #ADBAC7\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #DCBDFB\">setShowWhenLocked<\/span><span style=\"color: #ADBAC7\">(<\/span><span style=\"color: #6CB6FF\">true<\/span><span style=\"color: #ADBAC7\">)<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #768390\">\/\/ Close dialogs and window shade, so this is fully visible<\/span><\/span>\n<span class=\"line\"><span style=\"color: #DCBDFB\">sendBroadcast<\/span><span style=\"color: #ADBAC7\">(<\/span><span style=\"color: #DCBDFB\">Intent<\/span><span style=\"color: #ADBAC7\">(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #768390\">\/\/ Honor rotation on tablets; fix the orientation on phones.<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F47067\">if<\/span><span style=\"color: #ADBAC7\"> (<\/span><span style=\"color: #F47067\">!<\/span><span style=\"color: #DCBDFB\">getResources<\/span><span style=\"color: #ADBAC7\">().<\/span><span style=\"color: #DCBDFB\">getBoolean<\/span><span style=\"color: #ADBAC7\">(R.bool.rotateAlarmAlert)) {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">    <\/span><span style=\"color: #DCBDFB\">setRequestedOrientation<\/span><span style=\"color: #ADBAC7\">(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #ADBAC7\">}<\/span><\/span><\/code><\/pre><\/div>\n","protected":false},"excerpt":{"rendered":"<p>\ud0c0\uc774\uba38\ub97c \ub9cc\ub4e4\ub2e4\uac00, \uc548\ub4dc\ub85c\uc774\ub4dc\uc758 \uc624\ud508\uc18c\uc2a4 \uc571\uc778 DeskClock \uc18c\uc2a4\ub97c \uc880 \uc0b4\ud3b4\ubd24\ub2e4. \ud0c0\uc774\uba38 \ub3d9\uc791\uc2dc, AlarmManager\uc5d0 \uc644\ub8cc\uc2dc\uac04\uc744 \ub4f1\ub85d\ud55c\ub2e4. \uc2dc\uac04 \ubcc0\uacbd\uc2dc, AlarmManager\uc5d0 \ub4f1\ub85d\ud55c \uc54c\ub78c\uc744 \uc5c5\ub370\uc774\ud2b8 \uc2dc\ud0a8\ub2e4. TimerService\uac00 \uc2dc\uc791\ub418\uba74, onStartCommand()\uc5d0\uc11c \ub2e4\uc74c\uacfc\uac19\uc774 expireTimer()\ub97c \ubd88\ub7ec\uc900\ub2e4. TimerModel\uc758 expreTimer()\uac00 \ud638\ucd9c\ub418\uace0 \uc2e4\ud589\uc911\uc778 \uc11c\ube44\uc2a4\uac00 \uc800\uc7a5\ub41c\uac8c \uc5c6\ub2e4\uba74, \ub118\uaca8\ubc1b\uc740 \uc11c\ube44\uc2a4\ub97c \uc2e4\ud589\uc911 \uc11c\ube44\uc2a4\ub85c \uc124\uc815\ud55c\ub2e4. \uadf8\ub9ac\uace0 updateTimer()\ub97c \ubd88\ub7ec\uc900\ub2e4. updateTimer()\ub97c \ubcf4\uba74, \ud0c0\uc774\uba38\uac00 expired\ub410\uc744 \ub54c, updateHeadsUpNotification()\uc744 \ubd88\ub7ec\uc900\ub2e4. updateHeadsUpNotification()\uc5d0\uc11c\ub294 \uc11c\ube44\uc2a4\uc758 \uc720\ubb34\uc640 expired\ub41c \ud0c0\uc774\uba38\uc758 \uc720\ubb34\ub97c [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5,34],"tags":[186,311,312,38,283],"class_list":["post-1889","post","type-post","status-publish","format-standard","hentry","category-android","category-kotlin","tag-android-2","tag-deskclock","tag-expired","tag-kotlin","tag-timer"],"jetpack_featured_media_url":"","_links":{"self":[{"href":"http:\/\/batmask.net\/index.php\/wp-json\/wp\/v2\/posts\/1889","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/batmask.net\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/batmask.net\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/batmask.net\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/batmask.net\/index.php\/wp-json\/wp\/v2\/comments?post=1889"}],"version-history":[{"count":9,"href":"http:\/\/batmask.net\/index.php\/wp-json\/wp\/v2\/posts\/1889\/revisions"}],"predecessor-version":[{"id":3465,"href":"http:\/\/batmask.net\/index.php\/wp-json\/wp\/v2\/posts\/1889\/revisions\/3465"}],"wp:attachment":[{"href":"http:\/\/batmask.net\/index.php\/wp-json\/wp\/v2\/media?parent=1889"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/batmask.net\/index.php\/wp-json\/wp\/v2\/categories?post=1889"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/batmask.net\/index.php\/wp-json\/wp\/v2\/tags?post=1889"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}