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