/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.permissioncontroller.permission.data import android.app.Application import android.content.pm.PackageManager.NameNotFoundException import android.content.res.Resources.ID_NULL import android.os.UserHandle import android.util.Log import com.android.permissioncontroller.PermissionControllerApplication import java.io.FileNotFoundException import kotlinx.coroutines.Job import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParser.END_DOCUMENT import org.xmlpull.v1.XmlPullParser.END_TAG import org.xmlpull.v1.XmlPullParser.START_TAG private const val MANIFEST_FILE_NAME = "AndroidManifest.xml" private const val MANIFEST_TAG = "manifest" private const val PKG_ATTR = "package" private const val ATTRIBUTION_TAG = "attribution" private const val ANDROID_NS = "http://schemas.android.com/apk/res/android" private const val TAG_ATTR = "tag" private const val LABEL_ATTR = "label" /** * Label-resource-id of an attribution of a package/user. * *

Obviously the resource is found in the package, hence needs to be loaded via a Resources * object created for this package. */ class AttributionLabelLiveData private constructor( private val app: Application, private val attributionTag: String?, private val packageName: String, private val user: UserHandle ) : SmartAsyncMediatorLiveData(), PackageBroadcastReceiver.PackageBroadcastListener { private val LOG_TAG = AttributionLabelLiveData::class.java.simpleName override suspend fun loadDataAndPostValue(job: Job) { if (job.isCancelled) { return } if (attributionTag == null) { postValue(ID_NULL) return } val pkgContext = try { app.createPackageContextAsUser(packageName, 0, user) } catch (e: NameNotFoundException) { Log.e(LOG_TAG, "Cannot find $packageName for $user") postValue(null) return } // TODO (moltmann): Read this from PackageInfo once available var cookie = 0 while (true) { // Some resources have multiple "AndroidManifest.xml" loaded and hence we need // to find the right one cookie++ val parser = try { pkgContext.assets.openXmlResourceParser(cookie, MANIFEST_FILE_NAME) } catch (e: FileNotFoundException) { break } try { do { if (parser.eventType != START_TAG) { continue } if (parser.name != MANIFEST_TAG) { parser.skipTag() continue } // Ensure this is the right manifest if (parser.getAttributeValue(null, PKG_ATTR) != packageName) { break } while (parser.next() != END_TAG) { if (parser.eventType != START_TAG) { continue } if (parser.name != ATTRIBUTION_TAG) { parser.skipTag() continue } if (parser.getAttributeValue(ANDROID_NS, TAG_ATTR) == attributionTag) { postValue( parser.getAttributeResourceValue(ANDROID_NS, LABEL_ATTR, ID_NULL) ) return } else { parser.skipTag() } } } while (parser.next() != END_DOCUMENT) } finally { parser.close() } } postValue(null) } /** Skip tag parser is currently pointing to (including all tags nested in it) */ private fun XmlPullParser.skipTag() { var depth = 1 while (depth != 0) { when (next()) { END_TAG -> depth-- START_TAG -> depth++ } } } override fun onActive() { super.onActive() // Listen for changes to the attributions PackageBroadcastReceiver.addChangeCallback(packageName, this) update() } override fun onInactive() { super.onInactive() PackageBroadcastReceiver.removeChangeCallback(packageName, this) } override fun onPackageUpdate(packageName: String) { update() } /** * Repository for AttributionLiveData. * *

Key value is a pair of string attribution tag, string package name, user handle, value is * its corresponding LiveData. */ companion object : DataRepository, AttributionLabelLiveData>() { override fun newValue(key: Triple): AttributionLabelLiveData { return AttributionLabelLiveData( PermissionControllerApplication.get(), key.first, key.second, key.third ) } operator fun get( attributionTag: String?, packageName: String, user: UserHandle ): AttributionLabelLiveData = get(Triple(attributionTag, packageName, user)) } }