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