1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.util.kotlin
18 
19 import kotlinx.coroutines.CompletableDeferred
20 import kotlinx.coroutines.Job
21 import kotlinx.coroutines.channels.Channel
22 import kotlinx.coroutines.runBlocking
23 
24 /**
25  * A utility for handling incoming IPCs from a Binder interface in the order that they are received.
26  *
27  * This class serves as a replacement for the common [android.os.Handler] message-queue pattern,
28  * where IPCs can arrive on arbitrary threads and are all enqueued onto a queue and processed by the
29  * Handler in-order.
30  *
31  *     class MyService : Service() {
32  *
33  *       private val serializer = IpcSerializer()
34  *
35  *       // Need to invoke process() in order to actually process IPCs sent over the serializer.
36  *       override fun onStart(...) = lifecycleScope.launch {
37  *         serializer.process()
38  *       }
39  *
40  *       // In your binder implementation, use runSerializedBlocking to enqueue a function onto
41  *       // the serializer.
42  *       override fun onBind(intent: Intent?) = object : IAidlService.Stub() {
43  *         override fun ipcMethodFoo() = serializer.runSerializedBlocking {
44  *           ...
45  *         }
46  *
47  *         override fun ipcMethodBar() = serializer.runSerializedBlocking {
48  *           ...
49  *         }
50  *       }
51  *     }
52  */
53 class IpcSerializer {
54 
55     private val channel = Channel<Pair<CompletableDeferred<Unit>, Job>>()
56 
57     /**
58      * Runs functions enqueued via usage of [runSerialized] and [runSerializedBlocking] serially.
59      * This method will never complete normally, so it must be launched in its own coroutine; if
60      * this is not actively running, no enqueued functions will be evaluated.
61      */
62     suspend fun process(): Nothing {
63         for ((start, finish) in channel) {
64             // Signal to the sender that serializer has reached this message
65             start.complete(Unit)
66             // Wait to hear from the sender that it has finished running it's work, before handling
67             // the next message
68             finish.join()
69         }
70         error("Unexpected end of serialization channel")
71     }
72 
73     /**
74      * Enqueues [block] for evaluation by the serializer, suspending the caller until it has
75      * completed. It is up to the caller to define what thread this is evaluated in, determined
76      * by the [kotlin.coroutines.CoroutineContext] used.
77      */
78     suspend fun <R> runSerialized(block: suspend () -> R): R {
79         val start = CompletableDeferred(Unit)
80         val finish = CompletableDeferred(Unit)
81         // Enqueue our message on the channel.
82         channel.send(start to finish)
83         // Wait for the serializer to reach our message
84         start.await()
85         // Now evaluate the block
86         val result = block()
87         // Notify the serializer that we've completed evaluation
88         finish.complete(Unit)
89         return result
90     }
91 
92     /**
93      * Enqueues [block] for evaluation by the serializer, blocking the binder thread until it has
94      * completed. Evaluation occurs on the binder thread, so methods like
95      * [android.os.Binder.getCallingUid] that depend on the current thread will work as expected.
96      */
97     fun <R> runSerializedBlocking(block: suspend () -> R): R = runBlocking { runSerialized(block) }
98 }
99