Skip to content

배토의 개발일지

나를위한 단편적 기억들의 기록

Menu
  • 안드로이드
  • 코틀린
  • Godot
  • 블렌더
  • git
  • 게임제작일지
  • 기타
Menu

DeskClock 코드분석 #1 : 의 Timer expired시 구현 분석

Posted on 2023년 6월 6일2023년 6월 8일 by batmask

타이머를 만들다가, 안드로이드의 오픈소스 앱인 DeskClock 소스를 좀 살펴봤다.

타이머 동작시, AlarmManager에 완료시간을 등록한다. 시간 변경시, AlarmManager에 등록한 알람을 업데이트 시킨다.

@JvmStatic
fun createTimerExpiredIntent(context: Context, timer: Timer?): Intent {
    val timerId = timer?.id ?: -1
    return Intent(context, TimerService::class.java)
        .setAction(ACTION_TIMER_EXPIRED)
        .putExtra(EXTRA_TIMER_ID, timerId)
}
private fun updateAlarmManager() {
    // Locate the next firing timer if one exists.
    var nextExpiringTimer: Timer? = null
    for (timer in mutableTimers) {
        if (timer.isRunning) {
            if (nextExpiringTimer == null) {
                nextExpiringTimer = timer
            } else if (timer.expirationTime < nextExpiringTimer.expirationTime) {
                nextExpiringTimer = timer
            }
        }
    }

    // Build the intent that signals the timer expiration.
    val intent: Intent = TimerService.createTimerExpiredIntent(mContext, nextExpiringTimer)
    if (nextExpiringTimer == null) {
        // Cancel the existing timer expiration callback.
        val pi: PendingIntent? = PendingIntent.getService(mContext,
                0, intent, PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_NO_CREATE)
        if (pi != null) {
            mAlarmManager.cancel(pi)
            pi.cancel()
        }
    } else {
        // Update the existing timer expiration callback.
        val pi: PendingIntent = PendingIntent.getService(mContext,
                0, intent, PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_UPDATE_CURRENT)
        schedulePendingIntent(mAlarmManager, nextExpiringTimer.expirationTime, pi)
    }
}

TimerService가 시작되면, onStartCommand()에서 다음과같이 expireTimer()를 불러준다.

...
ACTION_TIMER_EXPIRED -> {
    Events.sendTimerEvent(R.string.action_fire, label)
    DataModel.dataModel.expireTimer(this, timer)
}
...

TimerModel의 expreTimer()가 호출되고 실행중인 서비스가 저장된게 없다면, 넘겨받은 서비스를 실행중 서비스로 설정한다. 그리고 updateTimer()를 불러준다.

fun expireTimer(service: Service?, timer: Timer) {
    if (mService == null) {
        // If this is the first expired timer, retain the service that will be used to start
        // the heads-up notification in the foreground.
        mService = service
    } else if (mService != service) {
        // If this is not the first expired timer, the service should match the one given when
        // the first timer expired.
        LogUtils.wtf("Expected TimerServices to be identical")
    }

    updateTimer(timer.expire())
}

updateTimer()를 보면, 타이머가 expired됐을 때, updateHeadsUpNotification()을 불러준다.

fun updateTimer(timer: Timer) {
    val before = doUpdateTimer(timer)

    // Update the notification after updating the timer data.
    updateNotification()

    // If the timer started or stopped being expired, update the heads-up notification.
    if (before.state != timer.state) {
        if (before.isExpired || timer.isExpired) {
            updateHeadsUpNotification()
        }
    }
}

updateHeadsUpNotification()에서는 서비스의 유무와 expired된 타이머의 유무를 체크하고 TimerNotificationBuilder의 buildHeadsUp()으로 알람용 Notification을 만든다. 그리고 service의 setForeground()로 foreground service로 Notification을 표시한다.

private fun updateHeadsUpNotification() {
    // Nothing can be done with the heads-up notification without a valid service reference.
    if (mService == null) {
        return
    }

    val expired = expiredTimers

    // If no expired timers exist, stop the service (which cancels the foreground notification).
    if (expired.isEmpty()) {
        mService!!.stopSelf()
        mService = null
        return
    }

    // Otherwise build and post a foreground notification reflecting the latest expired timers.
    val notification: Notification = mNotificationBuilder.buildHeadsUp(mContext, expired)
    val notificationId = mNotificationModel.expiredTimerNotificationId
    mService!!.startForeground(notificationId, notification)
}

expired된 타이머 알람용 Notification은 TimerNotificationBuilder에서 다음과 같이 만들어 주고 있다.

// Content intent shows the timer full screen when clicked.
val content = Intent(context, ExpiredTimersActivity::class.java)
val contentIntent: PendingIntent = Utils.pendingActivityIntent(context, content)

// Full screen intent has flags so it is different than the content intent.
val fullScreen: Intent = Intent(context, ExpiredTimersActivity::class.java)
                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NO_USER_ACTION)
val pendingFullScreen: PendingIntent = Utils.pendingActivityIntent(context, fullScreen)

val notification: Builder = Builder(
context, TIMER_MODEL_NOTIFICATION_CHANNEL_ID)
    .setOngoing(true)
    .setLocalOnly(true)
    .setShowWhen(false)
    .setAutoCancel(false)
    .setContentIntent(contentIntent)
    .setPriority(NotificationManager.IMPORTANCE_HIGH)
    .setDefaults(Notification.DEFAULT_LIGHTS)
    .setSmallIcon(R.drawable.stat_notify_timer)
    .setFullScreenIntent(pendingFullScreen, true)
    .setStyle(NotificationCompat.DecoratedCustomViewStyle())
    .setColor(ContextCompat.getColor(context, R.color.default_background))

완료된 타이머를 보여주기 위한 ExpiredTimersActivity를 화면에 띄우기 위해 다음과 같이 처리해주고 있다.

view.systemUiVisibility = View.SYSTEM_UI_FLAG_LOW_PROFILE

getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
    or WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON)

setTurnScreenOn(true)
setShowWhenLocked(true)

// Close dialogs and window shade, so this is fully visible
sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))

// Honor rotation on tablets; fix the orientation on phones.
if (!getResources().getBoolean(R.bool.rotateAlarmAlert)) {
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR)
}

답글 남기기 응답 취소

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

최신 글

  • Blender : snap을 써서 다른 vertex에 정렬하기
  • KTor Client 기본 사용 정리
  • 게임 만들거다.
  • Using Koin with Android Compose

보관함

2023 6월
일 월 화 수 목 금 토
 123
45678910
11121314151617
18192021222324
252627282930  
« 10월   7월 »

메타

  • 로그인
  • 엔트리 피드
  • 댓글 피드
  • WordPress.org
©2025 배토의 개발일지 | Built using WordPress and Responsive Blogily theme by Superb