blob: e1d65fb8c522f3b491186280595a32ddffa28f8a [file] [log] [blame]
Roman Elizarov23f864e2017-03-03 19:57:47 +03001/*
Roman Elizarov1f74a2d2018-06-29 19:19:45 +03002 * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
Roman Elizarov23f864e2017-03-03 19:57:47 +03003 */
4
5package kotlinx.coroutines.experimental.android
6
7import android.os.Handler
8import android.os.Looper
Roman Elizarovd1f50a32017-11-09 12:09:06 +03009import android.view.Choreographer
Roman Elizarov30dd5c12017-04-11 13:50:03 +030010import kotlinx.coroutines.experimental.*
Roman Elizarov23f864e2017-03-03 19:57:47 +030011import java.util.concurrent.TimeUnit
12import kotlin.coroutines.experimental.CoroutineContext
13
14/**
15 * Dispatches execution onto Android main UI thread and provides native [delay][Delay.delay] support.
16 */
17val UI = HandlerContext(Handler(Looper.getMainLooper()), "UI")
18
19/**
20 * Represents an arbitrary [Handler] as a implementation of [CoroutineDispatcher].
21 */
22fun Handler.asCoroutineDispatcher() = HandlerContext(this)
23
Roman Elizarov7c507aa2017-11-10 14:34:06 +030024private const val MAX_DELAY = Long.MAX_VALUE / 2 // cannot delay for too long on Android
25
Roman Elizarov23f864e2017-03-03 19:57:47 +030026/**
27 * Implements [CoroutineDispatcher] on top of an arbitrary Android [Handler].
28 * @param handler a handler.
29 * @param name an optional name for debugging.
30 */
31public class HandlerContext(
32 private val handler: Handler,
33 private val name: String? = null
34) : CoroutineDispatcher(), Delay {
Roman Elizarovd1f50a32017-11-09 12:09:06 +030035 @Volatile
36 private var _choreographer: Choreographer? = null
37
Roman Elizarov23f864e2017-03-03 19:57:47 +030038 override fun dispatch(context: CoroutineContext, block: Runnable) {
39 handler.post(block)
40 }
41
42 override fun scheduleResumeAfterDelay(time: Long, unit: TimeUnit, continuation: CancellableContinuation<Unit>) {
43 handler.postDelayed({
44 with(continuation) { resumeUndispatched(Unit) }
Roman Elizarov7c507aa2017-11-10 14:34:06 +030045 }, unit.toMillis(time).coerceAtMost(MAX_DELAY))
Roman Elizarov23f864e2017-03-03 19:57:47 +030046 }
47
48 override fun invokeOnTimeout(time: Long, unit: TimeUnit, block: Runnable): DisposableHandle {
Roman Elizarov7c507aa2017-11-10 14:34:06 +030049 handler.postDelayed(block, unit.toMillis(time).coerceAtMost(MAX_DELAY))
Roman Elizarov23f864e2017-03-03 19:57:47 +030050 return object : DisposableHandle {
51 override fun dispose() {
52 handler.removeCallbacks(block)
53 }
54 }
55 }
56
Roman Elizarovd1f50a32017-11-09 12:09:06 +030057 /**
58 * Awaits the next animation frame and returns frame time in nanoseconds.
59 */
60 public suspend fun awaitFrame(): Long {
61 // fast path when choreographer is already known
62 val choreographer = _choreographer
63 if (choreographer != null) {
64 return suspendCancellableCoroutine { cont ->
65 postFrameCallback(choreographer, cont)
66 }
67 }
68 // post into looper thread thread to figure it out
69 return suspendCancellableCoroutine { cont ->
70 handler.post {
71 updateChoreographerAndPostFrameCallback(cont)
72 }
73 }
74 }
75
76 private fun updateChoreographerAndPostFrameCallback(cont: CancellableContinuation<Long>) {
77 val choreographer = _choreographer ?:
78 Choreographer.getInstance()!!.also { _choreographer = it }
79 postFrameCallback(choreographer, cont)
80 }
81
82 private fun postFrameCallback(choreographer: Choreographer, cont: CancellableContinuation<Long>) {
83 choreographer.postFrameCallback { nanos ->
84 with(cont) { resumeUndispatched(nanos) }
85 }
86 }
87
Roman Elizarov23f864e2017-03-03 19:57:47 +030088 override fun toString() = name ?: handler.toString()
Roman Elizarov30dd5c12017-04-11 13:50:03 +030089 override fun equals(other: Any?): Boolean = other is HandlerContext && other.handler === handler
90 override fun hashCode(): Int = System.identityHashCode(handler)
Roman Elizarov23f864e2017-03-03 19:57:47 +030091}