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