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