1 /*
2  * Copyright 2023 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 androidx.datastore.testapp.twoWayIpc
18 
19 import android.os.Bundle
20 import android.os.Parcelable
21 import kotlin.properties.ReadWriteProperty
22 import kotlin.reflect.KProperty
23 import kotlinx.coroutines.CoroutineScope
24 
25 /**
26  * A class that represents a test subject for DataStore multi-process tests. Each test subject is
27  * given a [datastoreScope] as well as a [data] so they can keep state.
28  *
29  * Subjects execute [IpcAction]s which contain the actual test logic.
30  */
31 internal class TwoWayIpcSubject(val datastoreScope: CoroutineScope) {
32     val bus: TwoWayIpcBus =
33         TwoWayIpcBus(executionScope = datastoreScope, handler = this::handleIncomingAction)
34     val data = CompositeServiceSubjectModel()
35 
handleIncomingActionnull36     private suspend fun handleIncomingAction(bundle: Bundle?): Bundle {
37         @Suppress("DEPRECATION") val ipcAction = bundle?.getParcelable<IpcAction<*>>(KEY_ACTION)
38         checkNotNull(ipcAction) { "no ipc action in bundle" }
39         IpcLogger.log("executing action: ${ipcAction::class.java}")
40 
41         val response = ipcAction.invokeInRemoteProcess(this)
42         IpcLogger.log("executed action: ${ipcAction::class.java}")
43         return Bundle().also { it.putParcelable(KEY_RESPONSE, response) }
44     }
45 
invokeInRemoteProcessnull46     suspend fun <T : Parcelable> invokeInRemoteProcess(action: IpcAction<T>): T {
47         val response = bus.sendMessage(Bundle().also { it.putParcelable(KEY_ACTION, action) })
48         checkNotNull(response) { "No response received for $action" }
49         @Suppress("DEPRECATION")
50         return response.getParcelable(KEY_RESPONSE)
51             ?: error("didn't get a response from remote process")
52     }
53 
54     companion object {
55         private const val KEY_ACTION = "ipc_action"
56         private const val KEY_RESPONSE = "ipc_response"
57     }
58 }
59 
60 /**
61  * A property delegate to stash values into the [CompositeServiceSubjectModel] of a
62  * [TwoWayIpcSubject].
63  */
64 internal class SubjectReadWriteProperty<T>(private val key: CompositeServiceSubjectModel.Key<T>) :
65     ReadWriteProperty<TwoWayIpcSubject, T> {
getValuenull66     override fun getValue(thisRef: TwoWayIpcSubject, property: KProperty<*>): T {
67         return thisRef.data[key]
68     }
69 
setValuenull70     override fun setValue(thisRef: TwoWayIpcSubject, property: KProperty<*>, value: T) {
71         thisRef.data[key] = value
72     }
73 }
74