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 kotlinx.coroutines.Job 26 import org.xmlpull.v1.XmlPullParser 27 import org.xmlpull.v1.XmlPullParser.END_DOCUMENT 28 import org.xmlpull.v1.XmlPullParser.END_TAG 29 import org.xmlpull.v1.XmlPullParser.START_TAG 30 import java.io.FileNotFoundException 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 private constructor( 47 private val app: Application, 48 private val attributionTag: String?, 49 private val packageName: String, 50 private val user: UserHandle 51 ) : SmartAsyncMediatorLiveData<Int>(), PackageBroadcastReceiver.PackageBroadcastListener { 52 private val LOG_TAG = AttributionLabelLiveData::class.java.simpleName 53 loadDataAndPostValuenull54 override suspend fun loadDataAndPostValue(job: Job) { 55 if (job.isCancelled) { 56 return 57 } 58 59 if (attributionTag == null) { 60 postValue(ID_NULL) 61 return 62 } 63 64 val pkgContext = try { 65 app.createPackageContextAsUser(packageName, 0, user) 66 } catch (e: NameNotFoundException) { 67 Log.e(LOG_TAG, "Cannot find $packageName for $user") 68 69 postValue(null) 70 return 71 } 72 73 // TODO (moltmann): Read this from PackageInfo once available 74 var cookie = 0 75 while (true) { 76 // Some resources have multiple "AndroidManifest.xml" loaded and hence we need 77 // to find the right one 78 cookie++ 79 val parser = try { 80 pkgContext.assets.openXmlResourceParser(cookie, MANIFEST_FILE_NAME) 81 } catch (e: FileNotFoundException) { 82 break 83 } 84 85 try { 86 do { 87 if (parser.eventType != START_TAG) { 88 continue 89 } 90 91 if (parser.name != MANIFEST_TAG) { 92 parser.skipTag() 93 continue 94 } 95 96 // Ensure this is the right manifest 97 if (parser.getAttributeValue(null, PKG_ATTR) != packageName) { 98 break 99 } 100 101 while (parser.next() != END_TAG) { 102 if (parser.eventType != START_TAG) { 103 continue 104 } 105 106 if (parser.name != ATTRIBUTION_TAG) { 107 parser.skipTag() 108 continue 109 } 110 111 if (parser.getAttributeValue(ANDROID_NS, TAG_ATTR) == attributionTag) { 112 postValue(parser.getAttributeResourceValue(ANDROID_NS, LABEL_ATTR, 113 ID_NULL)) 114 return 115 } else { 116 parser.skipTag() 117 } 118 } 119 } while (parser.next() != END_DOCUMENT) 120 } finally { 121 parser.close() 122 } 123 } 124 125 postValue(null) 126 } 127 128 /** 129 * Skip tag parser is currently pointing to (including all tags nested in it) 130 */ skipTagnull131 private fun XmlPullParser.skipTag() { 132 var depth = 1 133 while (depth != 0) { 134 when (next()) { 135 END_TAG -> depth-- 136 START_TAG -> depth++ 137 } 138 } 139 } 140 onActivenull141 override fun onActive() { 142 super.onActive() 143 144 // Listen for changes to the attributions 145 PackageBroadcastReceiver.addChangeCallback(packageName, this) 146 update() 147 } 148 onInactivenull149 override fun onInactive() { 150 super.onInactive() 151 152 PackageBroadcastReceiver.removeChangeCallback(packageName, this) 153 } 154 onPackageUpdatenull155 override fun onPackageUpdate(packageName: String) { 156 update() 157 } 158 159 /** 160 * Repository for AttributionLiveData. 161 * <p> Key value is a pair of string attribution tag, string package name, user handle, value is 162 * its corresponding LiveData. 163 */ 164 companion object : DataRepository<Triple<String?, String, UserHandle>, 165 AttributionLabelLiveData>() { newValuenull166 override fun newValue(key: Triple<String?, String, UserHandle>): AttributionLabelLiveData { 167 return AttributionLabelLiveData(PermissionControllerApplication.get(), 168 key.first, key.second, key.third) 169 } 170 getnull171 operator fun get(attributionTag: String?, packageName: String, user: UserHandle): 172 AttributionLabelLiveData = 173 get(Triple(attributionTag, packageName, user)) 174 } 175 } 176