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.internal.pm.pkg.parsing; 18 19 import static com.android.internal.pm.pkg.parsing.ParsingPackageUtils.RIGID_PARSER; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.content.pm.parsing.result.ParseInput; 24 import android.content.pm.parsing.result.ParseResult; 25 import android.content.res.Resources; 26 import android.content.res.TypedArray; 27 import android.content.res.XmlResourceParser; 28 import android.os.Parcel; 29 import android.os.Parcelable; 30 import android.util.Pair; 31 import android.util.Slog; 32 33 import com.android.internal.pm.pkg.component.ParsedIntentInfo; 34 import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl; 35 import com.android.internal.util.Parcelling; 36 import com.android.internal.util.XmlUtils; 37 38 import org.xmlpull.v1.XmlPullParserException; 39 40 import java.io.IOException; 41 import java.util.ArrayList; 42 import java.util.List; 43 import java.util.Set; 44 45 /** @hide **/ 46 public class ParsingUtils { 47 48 public static final String TAG = "PackageParsing"; 49 50 public static final String ANDROID_RES_NAMESPACE = "http://schemas.android.com/apk/res/android"; 51 52 public static final int DEFAULT_MIN_SDK_VERSION = 1; 53 public static final int DEFAULT_MAX_SDK_VERSION = Integer.MAX_VALUE; 54 public static final int DEFAULT_TARGET_SDK_VERSION = 0; 55 56 public static final int NOT_SET = -1; 57 58 @Nullable buildClassName(String pkg, CharSequence clsSeq)59 public static String buildClassName(String pkg, CharSequence clsSeq) { 60 if (clsSeq == null || clsSeq.length() <= 0) { 61 return null; 62 } 63 String cls = clsSeq.toString(); 64 char c = cls.charAt(0); 65 if (c == '.') { 66 return pkg + cls; 67 } 68 if (cls.indexOf('.') < 0) { 69 StringBuilder b = new StringBuilder(pkg); 70 b.append('.'); 71 b.append(cls); 72 return b.toString(); 73 } 74 return cls; 75 } 76 77 @NonNull unknownTag(String parentTag, ParsingPackage pkg, XmlResourceParser parser, ParseInput input)78 public static ParseResult unknownTag(String parentTag, ParsingPackage pkg, 79 XmlResourceParser parser, ParseInput input) throws IOException, XmlPullParserException { 80 if (RIGID_PARSER) { 81 return input.error("Bad element under " + parentTag + ": " + parser.getName()); 82 } 83 Slog.w(TAG, "Unknown element under " + parentTag + ": " 84 + parser.getName() + " at " + pkg.getBaseApkPath() + " " 85 + parser.getPositionDescription()); 86 XmlUtils.skipCurrentTag(parser); 87 return input.success(null); // Type doesn't matter 88 } 89 90 /** 91 * Use with {@link Parcel#writeTypedList(List)} or 92 * {@link #writeInterfaceAsImplList(Parcel, List)} 93 * 94 * @see Parcel#createTypedArrayList(Parcelable.Creator) 95 */ 96 @NonNull createTypedInterfaceList( @onNull Parcel parcel, @NonNull Parcelable.Creator<Impl> creator)97 public static <Interface, Impl extends Interface> List<Interface> createTypedInterfaceList( 98 @NonNull Parcel parcel, @NonNull Parcelable.Creator<Impl> creator) { 99 int size = parcel.readInt(); 100 if (size < 0) { 101 return new ArrayList<>(); 102 } 103 ArrayList<Interface> list = new ArrayList<Interface>(size); 104 while (size > 0) { 105 list.add(parcel.readTypedObject(creator)); 106 size--; 107 } 108 return list; 109 } 110 111 /** 112 * Use with {@link #createTypedInterfaceList(Parcel, Parcelable.Creator)}. 113 * 114 * Writes a list that can be cast as Parcelable types at runtime. 115 * TODO: Remove with ImmutableList multi-casting support 116 * 117 * @see Parcel#writeTypedList(List) 118 */ 119 @NonNull writeParcelableList(@onNull Parcel parcel, List<?> list)120 public static void writeParcelableList(@NonNull Parcel parcel, List<?> list) { 121 if (list == null) { 122 parcel.writeInt(-1); 123 return; 124 } 125 int size = list.size(); 126 int index = 0; 127 parcel.writeInt(size); 128 while (index < size) { 129 parcel.writeTypedObject((Parcelable) list.get(index), 0); 130 index++; 131 } 132 } 133 134 public static class StringPairListParceler implements 135 Parcelling<List<Pair<String, ParsedIntentInfo>>> { 136 137 @Override parcel(List<Pair<String, ParsedIntentInfo>> item, Parcel dest, int parcelFlags)138 public void parcel(List<Pair<String, ParsedIntentInfo>> item, Parcel dest, 139 int parcelFlags) { 140 if (item == null) { 141 dest.writeInt(-1); 142 return; 143 } 144 145 final int size = item.size(); 146 dest.writeInt(size); 147 148 for (int index = 0; index < size; index++) { 149 Pair<String, ParsedIntentInfo> pair = item.get(index); 150 dest.writeString(pair.first); 151 dest.writeParcelable((Parcelable) pair.second, parcelFlags); 152 } 153 } 154 155 @Override unparcel(Parcel source)156 public List<Pair<String, ParsedIntentInfo>> unparcel(Parcel source) { 157 int size = source.readInt(); 158 if (size == -1) { 159 return null; 160 } 161 162 if (size == 0) { 163 return new ArrayList<>(0); 164 } 165 166 final List<Pair<String, ParsedIntentInfo>> list = new ArrayList<>(size); 167 for (int i = 0; i < size; ++i) { 168 list.add(Pair.create(source.readString(), source.readParcelable( 169 ParsedIntentInfoImpl.class.getClassLoader(), ParsedIntentInfo.class))); 170 } 171 172 return list; 173 } 174 } 175 176 /** 177 * Parse the {@link android.R.attr#knownActivityEmbeddingCerts} attribute, if available. 178 */ 179 @NonNull parseKnownActivityEmbeddingCerts(@onNull TypedArray sa, @NonNull Resources res, int resourceId, @NonNull ParseInput input)180 public static ParseResult<Set<String>> parseKnownActivityEmbeddingCerts(@NonNull TypedArray sa, 181 @NonNull Resources res, int resourceId, @NonNull ParseInput input) { 182 if (!sa.hasValue(resourceId)) { 183 return input.success(null); 184 } 185 186 final int knownActivityEmbeddingCertsResource = sa.getResourceId(resourceId, 0); 187 if (knownActivityEmbeddingCertsResource != 0) { 188 // The knownCerts attribute supports both a string array resource as well as a 189 // string resource for the case where the permission should only be granted to a 190 // single known signer. 191 Set<String> knownEmbeddingCertificates = null; 192 final String resourceType = res.getResourceTypeName( 193 knownActivityEmbeddingCertsResource); 194 if (resourceType.equals("array")) { 195 final String[] knownCerts = res.getStringArray(knownActivityEmbeddingCertsResource); 196 if (knownCerts != null) { 197 knownEmbeddingCertificates = Set.of(knownCerts); 198 } 199 } else { 200 final String knownCert = res.getString(knownActivityEmbeddingCertsResource); 201 if (knownCert != null) { 202 knownEmbeddingCertificates = Set.of(knownCert); 203 } 204 } 205 if (knownEmbeddingCertificates == null || knownEmbeddingCertificates.isEmpty()) { 206 return input.error("Defined a knownActivityEmbeddingCerts attribute but the " 207 + "provided resource is null"); 208 } 209 return input.success(knownEmbeddingCertificates); 210 } 211 212 // If the knownCerts resource ID is null - the app specified a string value for the 213 // attribute representing a single trusted signer. 214 final String knownCert = sa.getString(resourceId); 215 if (knownCert == null || knownCert.isEmpty()) { 216 return input.error("Defined a knownActivityEmbeddingCerts attribute but the provided " 217 + "string is empty"); 218 } 219 return input.success(Set.of(knownCert)); 220 } 221 } 222