1 /*
2  * Copyright (C) 2022 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.internal.pm.pkg.component;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.pm.parsing.result.ParseInput;
22 import android.content.pm.parsing.result.ParseResult;
23 import android.content.res.Resources;
24 import android.content.res.TypedArray;
25 import android.content.res.XmlResourceParser;
26 import android.util.ArraySet;
27 
28 import com.android.internal.R;
29 
30 import org.xmlpull.v1.XmlPullParser;
31 import org.xmlpull.v1.XmlPullParserException;
32 
33 import java.io.IOException;
34 import java.util.ArrayList;
35 import java.util.Collections;
36 import java.util.List;
37 
38 /** @hide */
39 public class ParsedAttributionUtils {
40 
41     @NonNull
parseAttribution(Resources res, XmlResourceParser parser, ParseInput input)42     public static ParseResult<ParsedAttribution> parseAttribution(Resources res,
43             XmlResourceParser parser, ParseInput input)
44             throws IOException, XmlPullParserException {
45         String attributionTag;
46         int label;
47         List<String> inheritFrom = null;
48 
49         TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestAttribution);
50         if (sa == null) {
51             return input.error("<attribution> could not be parsed");
52         }
53 
54         try {
55             attributionTag = sa.getNonConfigurationString(
56                     R.styleable.AndroidManifestAttribution_tag, 0);
57             if (attributionTag == null) {
58                 return input.error("<attribution> does not specify android:tag");
59             }
60             if (attributionTag.length() > ParsedAttribution.MAX_ATTRIBUTION_TAG_LEN) {
61                 return input.error("android:tag is too long. Max length is "
62                         + ParsedAttribution.MAX_ATTRIBUTION_TAG_LEN);
63             }
64 
65             label = sa.getResourceId(R.styleable.AndroidManifestAttribution_label, 0);
66             if (label == Resources.ID_NULL) {
67                 return input.error("<attribution> does not specify android:label");
68             }
69         } finally {
70             sa.recycle();
71         }
72 
73         int type;
74         final int innerDepth = parser.getDepth();
75         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
76                 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
77             if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
78                 continue;
79             }
80 
81             String tagName = parser.getName();
82             if (tagName.equals("inherit-from")) {
83                 sa = res.obtainAttributes(parser,
84                         R.styleable.AndroidManifestAttributionInheritFrom);
85                 if (sa == null) {
86                     return input.error("<inherit-from> could not be parsed");
87                 }
88 
89                 try {
90                     String inheritFromId = sa.getNonConfigurationString(
91                             R.styleable.AndroidManifestAttributionInheritFrom_tag, 0);
92 
93                     if (inheritFrom == null) {
94                         inheritFrom = new ArrayList<>();
95                     }
96                     inheritFrom.add(inheritFromId);
97                 } finally {
98                     sa.recycle();
99                 }
100             } else {
101                 return input.error("Bad element under <attribution>: " + tagName);
102             }
103         }
104 
105         if (inheritFrom == null) {
106             inheritFrom = Collections.emptyList();
107         } else {
108             ((ArrayList) inheritFrom).trimToSize();
109         }
110 
111         return input.success(new ParsedAttributionImpl(attributionTag, label, inheritFrom));
112     }
113 
114     /**
115      * @return Is this set of attributions a valid combination for a single package?
116      */
isCombinationValid(@ullable List<ParsedAttribution> attributions)117     public static boolean isCombinationValid(@Nullable List<ParsedAttribution> attributions) {
118         if (attributions == null) {
119             return true;
120         }
121 
122         ArraySet<String> attributionTags = new ArraySet<>(attributions.size());
123         ArraySet<String> inheritFromAttributionTags = new ArraySet<>();
124 
125         int numAttributions = attributions.size();
126         if (numAttributions > ParsedAttributionImpl.MAX_NUM_ATTRIBUTIONS) {
127             return false;
128         }
129 
130         for (int attributionNum = 0; attributionNum < numAttributions; attributionNum++) {
131             boolean wasAdded = attributionTags.add(attributions.get(attributionNum).getTag());
132             if (!wasAdded) {
133                 // feature id is not unique
134                 return false;
135             }
136         }
137 
138         for (int attributionNum = 0; attributionNum < numAttributions; attributionNum++) {
139             ParsedAttribution feature = attributions.get(attributionNum);
140 
141             final List<String> inheritFromList = feature.getInheritFrom();
142             int numInheritFrom = inheritFromList.size();
143             for (int inheritFromNum = 0; inheritFromNum < numInheritFrom; inheritFromNum++) {
144                 String inheritFrom = inheritFromList.get(inheritFromNum);
145 
146                 if (attributionTags.contains(inheritFrom)) {
147                     // Cannot inherit from a attribution that is still defined
148                     return false;
149                 }
150 
151                 boolean wasAdded = inheritFromAttributionTags.add(inheritFrom);
152                 if (!wasAdded) {
153                     // inheritFrom is not unique
154                     return false;
155                 }
156             }
157         }
158 
159         return true;
160     }
161 }
162