1 /*
<lambda>null2  * Copyright (C) 2021 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.server.permission.access
18 
19 import android.os.Handler
20 import android.os.Looper
21 import android.os.Message
22 import android.os.SystemClock
23 import android.os.UserHandle
24 import android.util.AtomicFile
25 import android.util.Slog
26 import android.util.SparseLongArray
27 import com.android.internal.annotations.GuardedBy
28 import com.android.modules.utils.BinaryXmlPullParser
29 import com.android.modules.utils.BinaryXmlSerializer
30 import com.android.server.IoThread
31 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
32 import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
33 import com.android.server.permission.access.util.PermissionApex
34 import com.android.server.permission.access.util.parseBinaryXml
35 import com.android.server.permission.access.util.readWithReserveCopy
36 import com.android.server.permission.access.util.serializeBinaryXml
37 import com.android.server.permission.access.util.writeWithReserveCopy
38 import java.io.File
39 import java.io.FileNotFoundException
40 
41 class AccessPersistence(private val policy: AccessPolicy) {
42     private val scheduleLock = Any()
43     @GuardedBy("scheduleLock") private val pendingMutationTimesMillis = SparseLongArray()
44     @GuardedBy("scheduleLock") private val pendingStates = MutableIntMap<AccessState>()
45     @GuardedBy("scheduleLock") private lateinit var writeHandler: WriteHandler
46 
47     private val writeLock = Any()
48 
49     fun initialize() {
50         writeHandler = WriteHandler(IoThread.getHandler().looper)
51     }
52 
53     /**
54      * Reads the state either from the disk or migrate legacy data when the data files are missing.
55      */
56     fun read(state: MutableAccessState) {
57         readSystemState(state)
58         state.externalState.userIds.forEachIndexed { _, userId -> readUserState(state, userId) }
59     }
60 
61     private fun readSystemState(state: MutableAccessState) {
62         val fileExists =
63             systemFile.parse {
64                 // This is the canonical way to call an extension function in a different class.
65                 // TODO(b/259469752): Use context receiver for this when it becomes stable.
66                 with(policy) { parseSystemState(state) }
67             }
68 
69         if (!fileExists) {
70             policy.migrateSystemState(state)
71             state.systemState.write(state, UserHandle.USER_ALL)
72         }
73     }
74 
75     private fun readUserState(state: MutableAccessState, userId: Int) {
76         val fileExists =
77             getUserFile(userId).parse { with(policy) { parseUserState(state, userId) } }
78 
79         if (!fileExists) {
80             policy.migrateUserState(state, userId)
81             state.userStates[userId]!!.write(state, userId)
82         }
83     }
84 
85     /**
86      * @return {@code true} if the file is successfully read from the disk; {@code false} if the
87      *   file doesn't exist yet.
88      */
89     private inline fun File.parse(block: BinaryXmlPullParser.() -> Unit): Boolean =
90         try {
91             AtomicFile(this).readWithReserveCopy { it.parseBinaryXml(block) }
92             true
93         } catch (e: FileNotFoundException) {
94             Slog.i(LOG_TAG, "$this not found")
95             false
96         } catch (e: Exception) {
97             throw IllegalStateException("Failed to read $this", e)
98         }
99 
100     fun write(state: AccessState) {
101         state.systemState.write(state, UserHandle.USER_ALL)
102         state.userStates.forEachIndexed { _, userId, userState -> userState.write(state, userId) }
103     }
104 
105     private fun WritableState.write(state: AccessState, userId: Int) {
106         when (val writeMode = writeMode) {
107             WriteMode.NONE -> {}
108             WriteMode.ASYNCHRONOUS -> {
109                 synchronized(scheduleLock) {
110                     writeHandler.removeMessages(userId)
111                     pendingStates[userId] = state
112                     // SystemClock.uptimeMillis() is used in Handler.sendMessageDelayed().
113                     val currentTimeMillis = SystemClock.uptimeMillis()
114                     val pendingMutationTimeMillis =
115                         pendingMutationTimesMillis.getOrPut(userId) { currentTimeMillis }
116                     val currentDelayMillis = currentTimeMillis - pendingMutationTimeMillis
117                     val message = writeHandler.obtainMessage(userId)
118                     if (currentDelayMillis > MAX_WRITE_DELAY_MILLIS) {
119                         message.sendToTarget()
120                     } else {
121                         val newDelayMillis =
122                             WRITE_DELAY_TIME_MILLIS.coerceAtMost(
123                                 MAX_WRITE_DELAY_MILLIS - currentDelayMillis
124                             )
125                         writeHandler.sendMessageDelayed(message, newDelayMillis)
126                     }
127                 }
128             }
129             WriteMode.SYNCHRONOUS -> {
130                 synchronized(scheduleLock) { pendingStates[userId] = state }
131                 writePendingState(userId)
132             }
133             else -> error(writeMode)
134         }
135     }
136 
137     private fun writePendingState(userId: Int) {
138         synchronized(writeLock) {
139             val state: AccessState?
140             synchronized(scheduleLock) {
141                 pendingMutationTimesMillis -= userId
142                 state = pendingStates.remove(userId)
143                 writeHandler.removeMessages(userId)
144             }
145             if (state == null) {
146                 return
147             }
148             if (userId == UserHandle.USER_ALL) {
149                 writeSystemState(state)
150             } else {
151                 writeUserState(state, userId)
152             }
153         }
154     }
155 
156     private fun writeSystemState(state: AccessState) {
157         systemFile.serialize { with(policy) { serializeSystemState(state) } }
158     }
159 
160     private fun writeUserState(state: AccessState, userId: Int) {
161         getUserFile(userId).serialize { with(policy) { serializeUserState(state, userId) } }
162     }
163 
164     private inline fun File.serialize(block: BinaryXmlSerializer.() -> Unit) {
165         try {
166             AtomicFile(this).writeWithReserveCopy { it.serializeBinaryXml(block) }
167         } catch (e: Exception) {
168             Slog.e(LOG_TAG, "Failed to serialize $this", e)
169         }
170     }
171 
172     private val systemFile: File
173         get() = File(PermissionApex.systemDataDirectory, FILE_NAME)
174 
175     private fun getUserFile(userId: Int): File =
176         File(PermissionApex.getUserDataDirectory(userId), FILE_NAME)
177 
178     companion object {
179         private val LOG_TAG = AccessPersistence::class.java.simpleName
180 
181         private const val FILE_NAME = "access.abx"
182 
183         private const val WRITE_DELAY_TIME_MILLIS = 1000L
184         private const val MAX_WRITE_DELAY_MILLIS = 2000L
185     }
186 
187     private inner class WriteHandler(looper: Looper) : Handler(looper) {
188         override fun handleMessage(message: Message) {
189             val userId = message.what
190             writePendingState(userId)
191         }
192     }
193 }
194