1 /* 2 * Copyright (C) 2022 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.provider.DeviceConfig 23 import android.util.Log 24 import android.util.Xml 25 import androidx.annotation.RequiresApi 26 import com.android.permissioncontroller.DeviceUtils 27 import com.android.permissioncontroller.PermissionControllerApplication 28 import com.android.permissioncontroller.permission.data.v33.PermissionDecision 29 import com.android.permissioncontroller.permission.service.BasePermissionEventStorage 30 import com.android.permissioncontroller.permission.service.PermissionEventStorage 31 import com.android.permissioncontroller.permission.utils.Utils 32 import kotlinx.coroutines.Dispatchers 33 import kotlinx.coroutines.GlobalScope 34 import kotlinx.coroutines.launch 35 import org.xmlpull.v1.XmlPullParser 36 import org.xmlpull.v1.XmlPullParserException 37 import java.io.IOException 38 import java.io.InputStream 39 import java.io.OutputStream 40 import java.nio.charset.StandardCharsets 41 import java.text.ParseException 42 import java.text.SimpleDateFormat 43 import java.util.Date 44 import java.util.Locale 45 import java.util.concurrent.TimeUnit 46 47 /** 48 * Implementation of [BasePermissionEventStorage] for storing [PermissionDecision] events. 49 */ 50 @RequiresApi(Build.VERSION_CODES.TIRAMISU) 51 class PermissionDecisionStorageImpl( 52 context: Context, 53 jobScheduler: JobScheduler = context.getSystemService(JobScheduler::class.java)!! 54 ) : BasePermissionEventStorage<PermissionDecision>(context, jobScheduler) { 55 56 // We don't use namespaces 57 private val ns: String? = null 58 59 /** 60 * The format for how dates are stored. 61 */ 62 private val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.US) 63 64 companion object { 65 private const val LOG_TAG = "PermissionDecisionStorageImpl" 66 67 private const val DB_VERSION = 1 68 69 /** 70 * Config store file name for general shared store file. 71 */ 72 private const val STORE_FILE_NAME = "recent_permission_decisions.xml" 73 74 private const val TAG_RECENT_PERMISSION_DECISIONS = "recent-permission-decisions" 75 private const val TAG_PERMISSION_DECISION = "permission-decision" 76 private const val ATTR_VERSION = "version" 77 private const val ATTR_PACKAGE_NAME = "package-name" 78 private const val ATTR_PERMISSION_GROUP = "permission-group-name" 79 private const val ATTR_DECISION_TIME = "decision-time" 80 private const val ATTR_IS_GRANTED = "is-granted" 81 82 private val DEFAULT_MAX_DATA_AGE_MS = TimeUnit.DAYS.toMillis(7) 83 84 @Volatile 85 private var INSTANCE: PermissionEventStorage<PermissionDecision>? = null 86 getInstancenull87 fun getInstance(): PermissionEventStorage<PermissionDecision> = 88 INSTANCE ?: synchronized(this) { 89 INSTANCE ?: createInstance().also { INSTANCE = it } 90 } 91 createInstancenull92 private fun createInstance(): PermissionEventStorage<PermissionDecision> { 93 return PermissionDecisionStorageImpl(PermissionControllerApplication.get()) 94 } 95 recordPermissionDecisionnull96 fun recordPermissionDecision( 97 context: Context, 98 packageName: String, 99 permGroupName: String, 100 isGranted: Boolean 101 ) { 102 if (isRecordPermissionsSupported(context)) { 103 GlobalScope.launch(Dispatchers.IO) { 104 getInstance().storeEvent( 105 PermissionDecision(packageName, System.currentTimeMillis(), permGroupName, 106 isGranted)) 107 } 108 } 109 } 110 isRecordPermissionsSupportednull111 fun isRecordPermissionsSupported(context: Context): Boolean { 112 return DeviceUtils.isAuto(context) 113 } 114 } 115 serializenull116 override fun serialize(stream: OutputStream, events: List<PermissionDecision>) { 117 val out = Xml.newSerializer() 118 out.setOutput(stream, StandardCharsets.UTF_8.name()) 119 out.startDocument(/* encoding= */ null, /* standalone= */ true) 120 out.startTag(ns, TAG_RECENT_PERMISSION_DECISIONS) 121 out.attribute(/* namespace= */ null, ATTR_VERSION, DB_VERSION.toString()) 122 for (decision in events) { 123 out.startTag(ns, TAG_PERMISSION_DECISION) 124 out.attribute(ns, ATTR_PACKAGE_NAME, decision.packageName) 125 out.attribute(ns, ATTR_PERMISSION_GROUP, decision.permissionGroupName) 126 val date = dateFormat.format(Date(decision.eventTime)) 127 out.attribute(ns, ATTR_DECISION_TIME, date) 128 out.attribute(ns, ATTR_IS_GRANTED, decision.isGranted.toString()) 129 out.endTag(ns, TAG_PERMISSION_DECISION) 130 } 131 out.endTag(/* namespace= */ null, TAG_RECENT_PERMISSION_DECISIONS) 132 out.endDocument() 133 } 134 parsenull135 override fun parse(inputStream: InputStream): List<PermissionDecision> { 136 inputStream.use { 137 val parser: XmlPullParser = Xml.newPullParser() 138 parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false) 139 parser.setInput(inputStream, /* inputEncoding= */ null) 140 parser.nextTag() 141 return readRecentDecisions(parser) 142 } 143 } 144 145 @Throws(XmlPullParserException::class, IOException::class) readRecentDecisionsnull146 private fun readRecentDecisions(parser: XmlPullParser): List<PermissionDecision> { 147 val entries = mutableListOf<PermissionDecision>() 148 149 parser.require(XmlPullParser.START_TAG, ns, TAG_RECENT_PERMISSION_DECISIONS) 150 while (parser.next() != XmlPullParser.END_TAG) { 151 readPermissionDecision(parser)?.let { 152 entries.add(it) 153 } 154 } 155 return entries 156 } 157 158 @Throws(XmlPullParserException::class, IOException::class) readPermissionDecisionnull159 private fun readPermissionDecision(parser: XmlPullParser): PermissionDecision? { 160 var decision: PermissionDecision? = null 161 parser.require(XmlPullParser.START_TAG, ns, TAG_PERMISSION_DECISION) 162 try { 163 val packageName = parser.getAttributeValueNullSafe(ns, ATTR_PACKAGE_NAME) 164 val permissionGroup = parser.getAttributeValueNullSafe(ns, ATTR_PERMISSION_GROUP) 165 val decisionDate = parser.getAttributeValueNullSafe(ns, ATTR_DECISION_TIME) 166 val decisionTime = dateFormat.parse(decisionDate)?.time 167 ?: throw IllegalArgumentException( 168 "Could not parse date $decisionDate on package $packageName") 169 val isGranted = parser.getAttributeValueNullSafe(ns, ATTR_IS_GRANTED).toBoolean() 170 decision = PermissionDecision(packageName, decisionTime, permissionGroup, isGranted) 171 } catch (e: XmlPullParserException) { 172 Log.e(LOG_TAG, "Unable to parse permission decision", e) 173 } catch (e: ParseException) { 174 Log.e(LOG_TAG, "Unable to parse permission decision", e) 175 } catch (e: IllegalArgumentException) { 176 Log.e(LOG_TAG, "Unable to parse permission decision", e) 177 } finally { 178 parser.nextTag() 179 parser.require(XmlPullParser.END_TAG, ns, TAG_PERMISSION_DECISION) 180 } 181 return decision 182 } 183 184 @Throws(XmlPullParserException::class) getAttributeValueNullSafenull185 private fun XmlPullParser.getAttributeValueNullSafe(namespace: String?, name: String): String { 186 return this.getAttributeValue(namespace, name) 187 ?: throw XmlPullParserException( 188 "Could not find attribute: namespace $namespace, name $name") 189 } 190 getDatabaseFileNamenull191 override fun getDatabaseFileName(): String { 192 return STORE_FILE_NAME 193 } 194 getMaxDataAgeMsnull195 override fun getMaxDataAgeMs(): Long { 196 return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS, 197 Utils.PROPERTY_PERMISSION_DECISIONS_MAX_DATA_AGE_MILLIS, 198 DEFAULT_MAX_DATA_AGE_MS) 199 } 200 hasTheSamePrimaryKeynull201 override fun hasTheSamePrimaryKey( 202 first: PermissionDecision, 203 second: PermissionDecision 204 ): Boolean { 205 return first.packageName == second.packageName && 206 first.permissionGroupName == second.permissionGroupName 207 } 208 copyWithTimeDeltanull209 override fun PermissionDecision.copyWithTimeDelta(timeDelta: Long): PermissionDecision { 210 return this.copy(eventTime = this.eventTime + timeDelta) 211 } 212 } 213