1 /*
2 * Copyright (C) 2023 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.tools.metalava.manifest
18
19 import com.android.SdkConstants
20 import com.android.tools.metalava.model.MinSdkVersion
21 import com.android.tools.metalava.model.SetMinSdkVersion
22 import com.android.tools.metalava.model.UnsetMinSdkVersion
23 import com.android.tools.metalava.reporter.Issues
24 import com.android.tools.metalava.reporter.Reporter
25 import com.android.tools.metalava.xml.parseDocument
26 import com.android.utils.XmlUtils
27 import java.io.File
28
29 /**
30 * An empty manifest. This is safe to share as while it is not strictly immutable it only mutates
31 * the object when lazily initializing [Manifest.info].
32 */
<lambda>null33 val emptyManifest: Manifest by lazy { Manifest(null, null) }
34
35 private data class ManifestInfo(
36 val permissions: Map<String, String>,
37 val minSdkVersion: MinSdkVersion
38 )
39
40 private val defaultInfo = ManifestInfo(emptyMap(), UnsetMinSdkVersion)
41
42 /** Provides access to information from an `AndroidManifest.xml` file. */
43 class Manifest(private val manifest: File?, private val reporter: Reporter?) {
44
<lambda>null45 private val info: ManifestInfo by lazy { readManifestInfo() }
46
readManifestInfonull47 private fun readManifestInfo(): ManifestInfo {
48 if (manifest == null) {
49 return defaultInfo
50 }
51
52 return try {
53 val doc = parseDocument(manifest.readText(Charsets.UTF_8), true)
54
55 // Extract permissions.
56 val map = HashMap<String, String>(600)
57 var current =
58 XmlUtils.getFirstSubTagByName(doc.documentElement, SdkConstants.TAG_PERMISSION)
59 while (current != null) {
60 val permissionName =
61 current.getAttributeNS(SdkConstants.ANDROID_URI, SdkConstants.ATTR_NAME)
62 val protectionLevel =
63 current.getAttributeNS(SdkConstants.ANDROID_URI, "protectionLevel")
64 map[permissionName] = protectionLevel
65 current = XmlUtils.getNextTagByName(current, SdkConstants.TAG_PERMISSION)
66 }
67
68 // Extract minSdkVersion.
69 val min: MinSdkVersion = run {
70 val usesSdk =
71 XmlUtils.getFirstSubTagByName(doc.documentElement, SdkConstants.TAG_USES_SDK)
72 if (usesSdk == null) {
73 UnsetMinSdkVersion
74 } else {
75 val value =
76 usesSdk.getAttributeNS(
77 SdkConstants.ANDROID_URI,
78 SdkConstants.ATTR_MIN_SDK_VERSION
79 )
80 if (value.isEmpty()) UnsetMinSdkVersion else SetMinSdkVersion(value.toInt())
81 }
82 }
83
84 ManifestInfo(map, min)
85 } catch (error: Throwable) {
86 reporter?.report(
87 Issues.PARSE_ERROR,
88 manifest,
89 "Failed to parse $manifest: ${error.message}"
90 )
91 ?: throw error
92 defaultInfo
93 }
94 }
95
96 /** Check whether the manifest is empty or not. */
isEmptynull97 fun isEmpty(): Boolean {
98 return manifest == null
99 }
100
101 /**
102 * Returns the permission level of the named permission, if specified in the manifest. This
103 * method should only be called if the codebase has been configured with a manifest
104 */
getPermissionLevelnull105 fun getPermissionLevel(name: String): String? {
106 assert(manifest != null) {
107 "This method should only be called when a manifest has been configured on the codebase"
108 }
109
110 return info.permissions[name]
111 }
112
getMinSdkVersionnull113 fun getMinSdkVersion(): MinSdkVersion {
114 return info.minSdkVersion
115 }
116
toStringnull117 override fun toString(): String {
118 return manifest.toString()
119 }
120 }
121