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