• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.permissioncontroller.permission.service.v33
18 
19 import android.app.job.JobScheduler
20 import android.content.Context
21 import android.os.Build
22 import android.util.AtomicFile
23 import android.util.Log
24 import androidx.annotation.RequiresApi
25 import com.android.permissioncontroller.DumpableLog
26 import com.android.permissioncontroller.permission.data.v33.PermissionEvent
27 import org.xmlpull.v1.XmlPullParserException
28 import java.io.File
29 import java.io.FileOutputStream
30 import java.io.IOException
31 import java.io.InputStream
32 import java.io.OutputStream
33 
34 /**
35  * Thread-safe implementation of [PermissionEventStorage] using an XML file as the
36  * database.
37  */
38 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
39 abstract class BasePermissionEventStorage<T : PermissionEvent>(
40     private val context: Context,
41     jobScheduler: JobScheduler = context.getSystemService(JobScheduler::class.java)!!
42 ) : PermissionEventStorage<T> {
43 
44     private val dbFile: AtomicFile = AtomicFile(File(context.filesDir, getDatabaseFileName()))
45     private val fileLock = Object()
46 
47     companion object {
48         private const val LOG_TAG = "BasePermissionEventStorage"
49     }
50 
51     init {
52         PermissionEventCleanupJobService.scheduleOldDataCleanupIfNecessary(context, jobScheduler)
53     }
54 
storeEventnull55     override suspend fun storeEvent(event: T): Boolean {
56         synchronized(fileLock) {
57             val existingEvents = readData()
58 
59             val newEvents = mutableListOf<T>()
60             // add new event first to keep the list ordered
61             newEvents.add(event)
62             for (existingEvent in existingEvents) {
63                 // ignore any old events that violate the primary key uniqueness with the database
64                 if (hasTheSamePrimaryKey(existingEvent, event)) {
65                     continue
66                 }
67                 newEvents.add(existingEvent)
68             }
69 
70             return writeData(newEvents)
71         }
72     }
73 
loadEventsnull74     override suspend fun loadEvents(): List<T> {
75         synchronized(fileLock) {
76             return readData()
77         }
78     }
79 
clearEventsnull80     override suspend fun clearEvents() {
81         synchronized(fileLock) {
82             dbFile.delete()
83         }
84     }
85 
removeOldDatanull86     override suspend fun removeOldData(): Boolean {
87         synchronized(fileLock) {
88             val existingEvents = readData()
89 
90             val originalCount = existingEvents.size
91             val newEvents = existingEvents.filter {
92                 (System.currentTimeMillis() - it.eventTime) <= getMaxDataAgeMs()
93             }
94 
95             DumpableLog.d(LOG_TAG,
96                 "${originalCount - newEvents.size} old permission events removed")
97 
98             return writeData(newEvents)
99         }
100     }
101 
removeEventsForPackagenull102     override suspend fun removeEventsForPackage(packageName: String): Boolean {
103         synchronized(fileLock) {
104             val existingEvents = readData()
105 
106             val newEvents = existingEvents.filter { it.packageName != packageName }
107             return writeData(newEvents)
108         }
109     }
110 
updateEventsBySystemTimeDeltanull111     override suspend fun updateEventsBySystemTimeDelta(diffSystemTimeMillis: Long): Boolean {
112         synchronized(fileLock) {
113             val existingEvents = readData()
114 
115             val newEvents = existingEvents.map {
116                 it.copyWithTimeDelta(diffSystemTimeMillis)
117             }
118             return writeData(newEvents)
119         }
120     }
121 
writeDatanull122     private fun writeData(events: List<T>): Boolean {
123         val stream: FileOutputStream = try {
124             dbFile.startWrite()
125         } catch (e: IOException) {
126             Log.e(LOG_TAG, "Failed to save db file", e)
127             return false
128         }
129         try {
130             serialize(stream, events)
131             dbFile.finishWrite(stream)
132         } catch (e: IOException) {
133             Log.e(LOG_TAG, "Failed to save db file, restoring backup", e)
134             dbFile.failWrite(stream)
135             return false
136         }
137 
138         return true
139     }
140 
readDatanull141     private fun readData(): List<T> {
142         if (!dbFile.baseFile.exists()) {
143             return emptyList()
144         }
145         return try {
146             parse(dbFile.openRead())
147         } catch (e: IOException) {
148             Log.e(LOG_TAG, "Failed to read db file", e)
149             emptyList()
150         } catch (e: XmlPullParserException) {
151             Log.e(LOG_TAG, "Failed to read db file", e)
152             emptyList()
153         }
154     }
155 
156     /**
157      * Serialize a list of permission events.
158      *
159      * @param stream output stream to serialize events to
160      * @param events list of permission events to serialize
161      */
serializenull162     abstract fun serialize(stream: OutputStream, events: List<T>)
163 
164     /**
165      * Parse a list of permission events from the XML parser.
166      *
167      * @param inputStream input stream to parse events from
168      * @return the list of parsed permission events
169      */
170     @Throws(XmlPullParserException::class, IOException::class)
171     abstract fun parse(inputStream: InputStream): List<T>
172 
173     /**
174      * Returns file name for database.
175      */
176     abstract fun getDatabaseFileName(): String
177 
178     /**
179      * Returns max time that data should be persisted before being removed.
180      */
181     abstract fun getMaxDataAgeMs(): Long
182 
183     /**
184      * Returns true if the two events have the same primary key for the database store.
185      */
186     abstract fun hasTheSamePrimaryKey(first: T, second: T): Boolean
187 
188     /**
189      * Copies the event with the time delta applied to the [PermissionEvent.eventTime].
190      */
191     abstract fun T.copyWithTimeDelta(timeDelta: Long): T
192 }
193