• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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