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