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