1 /*
2  * Copyright (C) 2020 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.data
18 
19 import android.app.Application
20 import android.content.pm.PackageManager.NameNotFoundException
21 import android.content.res.Resources.ID_NULL
22 import android.os.UserHandle
23 import android.util.Log
24 import com.android.permissioncontroller.PermissionControllerApplication
25 import java.io.FileNotFoundException
26 import kotlinx.coroutines.Job
27 import org.xmlpull.v1.XmlPullParser
28 import org.xmlpull.v1.XmlPullParser.END_DOCUMENT
29 import org.xmlpull.v1.XmlPullParser.END_TAG
30 import org.xmlpull.v1.XmlPullParser.START_TAG
31 
32 private const val MANIFEST_FILE_NAME = "AndroidManifest.xml"
33 private const val MANIFEST_TAG = "manifest"
34 private const val PKG_ATTR = "package"
35 private const val ATTRIBUTION_TAG = "attribution"
36 private const val ANDROID_NS = "http://schemas.android.com/apk/res/android"
37 private const val TAG_ATTR = "tag"
38 private const val LABEL_ATTR = "label"
39 
40 /**
41  * Label-resource-id of an attribution of a package/user.
42  *
43  * <p>Obviously the resource is found in the package, hence needs to be loaded via a Resources
44  * object created for this package.
45  */
46 class AttributionLabelLiveData
47 private constructor(
48     private val app: Application,
49     private val attributionTag: String?,
50     private val packageName: String,
51     private val user: UserHandle
52 ) : SmartAsyncMediatorLiveData<Int>(), PackageBroadcastReceiver.PackageBroadcastListener {
53     private val LOG_TAG = AttributionLabelLiveData::class.java.simpleName
54 
loadDataAndPostValuenull55     override suspend fun loadDataAndPostValue(job: Job) {
56         if (job.isCancelled) {
57             return
58         }
59 
60         if (attributionTag == null) {
61             postValue(ID_NULL)
62             return
63         }
64 
65         val pkgContext =
66             try {
67                 app.createPackageContextAsUser(packageName, 0, user)
68             } catch (e: NameNotFoundException) {
69                 Log.e(LOG_TAG, "Cannot find $packageName for $user")
70 
71                 postValue(null)
72                 return
73             }
74 
75         // TODO (moltmann): Read this from PackageInfo once available
76         var cookie = 0
77         while (true) {
78             // Some resources have multiple "AndroidManifest.xml" loaded and hence we need
79             // to find the right one
80             cookie++
81             val parser =
82                 try {
83                     pkgContext.assets.openXmlResourceParser(cookie, MANIFEST_FILE_NAME)
84                 } catch (e: FileNotFoundException) {
85                     break
86                 }
87 
88             try {
89                 do {
90                     if (parser.eventType != START_TAG) {
91                         continue
92                     }
93 
94                     if (parser.name != MANIFEST_TAG) {
95                         parser.skipTag()
96                         continue
97                     }
98 
99                     // Ensure this is the right manifest
100                     if (parser.getAttributeValue(null, PKG_ATTR) != packageName) {
101                         break
102                     }
103 
104                     while (parser.next() != END_TAG) {
105                         if (parser.eventType != START_TAG) {
106                             continue
107                         }
108 
109                         if (parser.name != ATTRIBUTION_TAG) {
110                             parser.skipTag()
111                             continue
112                         }
113 
114                         if (parser.getAttributeValue(ANDROID_NS, TAG_ATTR) == attributionTag) {
115                             postValue(
116                                 parser.getAttributeResourceValue(ANDROID_NS, LABEL_ATTR, ID_NULL)
117                             )
118                             return
119                         } else {
120                             parser.skipTag()
121                         }
122                     }
123                 } while (parser.next() != END_DOCUMENT)
124             } finally {
125                 parser.close()
126             }
127         }
128 
129         postValue(null)
130     }
131 
132     /** Skip tag parser is currently pointing to (including all tags nested in it) */
skipTagnull133     private fun XmlPullParser.skipTag() {
134         var depth = 1
135         while (depth != 0) {
136             when (next()) {
137                 END_TAG -> depth--
138                 START_TAG -> depth++
139             }
140         }
141     }
142 
onActivenull143     override fun onActive() {
144         super.onActive()
145 
146         // Listen for changes to the attributions
147         PackageBroadcastReceiver.addChangeCallback(packageName, this)
148         update()
149     }
150 
onInactivenull151     override fun onInactive() {
152         super.onInactive()
153 
154         PackageBroadcastReceiver.removeChangeCallback(packageName, this)
155     }
156 
onPackageUpdatenull157     override fun onPackageUpdate(packageName: String) {
158         update()
159     }
160 
161     /**
162      * Repository for AttributionLiveData.
163      *
164      * <p> Key value is a pair of string attribution tag, string package name, user handle, value is
165      * its corresponding LiveData.
166      */
167     companion object :
168         DataRepository<Triple<String?, String, UserHandle>, AttributionLabelLiveData>() {
newValuenull169         override fun newValue(key: Triple<String?, String, UserHandle>): AttributionLabelLiveData {
170             return AttributionLabelLiveData(
171                 PermissionControllerApplication.get(),
172                 key.first,
173                 key.second,
174                 key.third
175             )
176         }
177 
getnull178         operator fun get(
179             attributionTag: String?,
180             packageName: String,
181             user: UserHandle
182         ): AttributionLabelLiveData = get(Triple(attributionTag, packageName, user))
183     }
184 }
185