1 /*
<lambda>null2  * 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 // Parcelize object is testing internal implementation of datastore-core library
18 @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
19 
20 package androidx.datastore.testapp.multiprocess.ipcActions
21 
22 import android.annotation.SuppressLint
23 import android.os.Parcelable
24 import androidx.datastore.core.CorruptionHandler
25 import androidx.datastore.core.DataStore
26 import androidx.datastore.core.DataStoreImpl
27 import androidx.datastore.core.FileStorage
28 import androidx.datastore.core.MultiProcessCoordinator
29 import androidx.datastore.core.Serializer
30 import androidx.datastore.core.handlers.NoOpCorruptionHandler
31 import androidx.datastore.core.okio.OkioSerializer
32 import androidx.datastore.core.okio.OkioStorage
33 import androidx.datastore.testapp.ProtoOkioSerializer
34 import androidx.datastore.testapp.ProtoSerializer
35 import androidx.datastore.testapp.twoWayIpc.CompositeServiceSubjectModel
36 import androidx.datastore.testapp.twoWayIpc.IpcAction
37 import androidx.datastore.testapp.twoWayIpc.SubjectReadWriteProperty
38 import androidx.datastore.testapp.twoWayIpc.TwoWayIpcSubject
39 import androidx.datastore.testing.TestMessageProto.FooProto
40 import com.google.protobuf.ExtensionRegistryLite
41 import java.io.File
42 import kotlinx.coroutines.CoroutineScope
43 import kotlinx.coroutines.Dispatchers
44 import kotlinx.parcelize.Parcelize
45 import okio.FileSystem
46 import okio.Path.Companion.toPath
47 
48 private val PROTO_SERIALIZER: Serializer<FooProto> =
49     ProtoSerializer<FooProto>(
50         FooProto.getDefaultInstance(),
51         ExtensionRegistryLite.getEmptyRegistry()
52     )
53 private val PROTO_OKIO_SERIALIZER: OkioSerializer<FooProto> =
54     ProtoOkioSerializer<FooProto>(
55         FooProto.getDefaultInstance(),
56         ExtensionRegistryLite.getEmptyRegistry()
57     )
58 
59 internal enum class StorageVariant {
60     FILE,
61     OKIO
62 }
63 
64 /** Creates the same datastore in current process as well as in the other given [subjects]. */
createMultiProcessTestDatastorenull65 internal suspend fun createMultiProcessTestDatastore(
66     filePath: String,
67     storageVariant: StorageVariant,
68     hostDatastoreScope: CoroutineScope,
69     corruptionHandler: Class<out CorruptionHandler<FooProto>>? = null,
70     vararg subjects: TwoWayIpcSubject
71 ): DataStore<FooProto> {
72     val currentProcessDatastore =
73         createDatastore(
74             filePath = filePath,
75             storageVariant = storageVariant,
76             datastoreScope = hostDatastoreScope,
77             corruptionHandler = corruptionHandler,
78         )
79     subjects.forEach {
80         it.invokeInRemoteProcess(
81             CreateDatastoreAction(
82                 filePath = filePath,
83                 storageVariant = storageVariant,
84                 corruptionHandler = corruptionHandler,
85             )
86         )
87     }
88     return currentProcessDatastore
89 }
90 
createDatastorenull91 private fun createDatastore(
92     filePath: String,
93     storageVariant: StorageVariant,
94     datastoreScope: CoroutineScope,
95     corruptionHandler: Class<out CorruptionHandler<FooProto>>?
96 ): DataStoreImpl<FooProto> {
97     val file = File(filePath)
98     val produceFile = { file }
99     val storage =
100         if (storageVariant == StorageVariant.FILE) {
101             FileStorage(
102                 PROTO_SERIALIZER,
103                 { MultiProcessCoordinator(Dispatchers.Default, it) },
104                 produceFile
105             )
106         } else {
107             OkioStorage(
108                 FileSystem.SYSTEM,
109                 PROTO_OKIO_SERIALIZER,
110                 { path, _ -> MultiProcessCoordinator(Dispatchers.Default, path.toFile()) },
111                 { file.absolutePath.toPath() }
112             )
113         }
114     val corruptionHandlerInstance =
115         corruptionHandler?.getDeclaredConstructor()?.also { it.isAccessible = true }?.newInstance()
116             ?: NoOpCorruptionHandler()
117     return DataStoreImpl(
118         storage = storage,
119         scope = datastoreScope,
120         corruptionHandler = corruptionHandlerInstance
121     )
122 }
123 
124 @SuppressLint("BanParcelableUsage")
125 @Parcelize
126 private class CreateDatastoreAction(
127     private val filePath: String,
128     private val storageVariant: StorageVariant,
129     private val corruptionHandler: Class<out CorruptionHandler<FooProto>>?
130 ) : IpcAction<CreateDatastoreAction>(), Parcelable {
invokeInRemoteProcessnull131     override suspend fun invokeInRemoteProcess(subject: TwoWayIpcSubject): CreateDatastoreAction {
132         val store =
133             createDatastore(filePath, storageVariant, subject.datastoreScope, corruptionHandler)
134         subject.datastore = store
135         return this
136     }
137 }
138 
139 private val DATASTORE_KEY = CompositeServiceSubjectModel.Key<DataStoreImpl<FooProto>>()
140 
141 internal var TwoWayIpcSubject.datastore by SubjectReadWriteProperty(DATASTORE_KEY)
142