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.server.pm.pkg.component; 18 19 import static com.android.server.pm.pkg.parsing.ParsingUtils.ANDROID_RES_NAMESPACE; 20 21 import android.annotation.NonNull; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.content.pm.parsing.result.ParseInput; 25 import android.content.pm.parsing.result.ParseResult; 26 import android.content.res.Resources; 27 import android.content.res.TypedArray; 28 import android.content.res.XmlResourceParser; 29 import android.os.PatternMatcher; 30 import android.util.Slog; 31 import android.util.TypedValue; 32 33 import com.android.internal.R; 34 import com.android.server.pm.pkg.parsing.ParsingPackage; 35 import com.android.server.pm.pkg.parsing.ParsingPackageUtils; 36 import com.android.server.pm.pkg.parsing.ParsingUtils; 37 38 import org.xmlpull.v1.XmlPullParser; 39 import org.xmlpull.v1.XmlPullParserException; 40 41 import java.io.IOException; 42 import java.util.Iterator; 43 44 /** @hide */ 45 public class ParsedIntentInfoUtils { 46 47 private static final String TAG = ParsingUtils.TAG; 48 49 public static final boolean DEBUG = false; 50 51 @NonNull parseIntentInfo(String className, ParsingPackage pkg, Resources res, XmlResourceParser parser, boolean allowGlobs, boolean allowAutoVerify, ParseInput input)52 public static ParseResult<ParsedIntentInfoImpl> parseIntentInfo(String className, 53 ParsingPackage pkg, Resources res, XmlResourceParser parser, boolean allowGlobs, 54 boolean allowAutoVerify, ParseInput input) 55 throws XmlPullParserException, IOException { 56 ParsedIntentInfoImpl intentInfo = new ParsedIntentInfoImpl(); 57 IntentFilter intentFilter = intentInfo.getIntentFilter(); 58 TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestIntentFilter); 59 try { 60 intentFilter.setPriority( 61 sa.getInt(R.styleable.AndroidManifestIntentFilter_priority, 0)); 62 intentFilter.setOrder(sa.getInt(R.styleable.AndroidManifestIntentFilter_order, 0)); 63 64 TypedValue v = sa.peekValue(R.styleable.AndroidManifestIntentFilter_label); 65 if (v != null) { 66 intentInfo.setLabelRes(v.resourceId); 67 if (v.resourceId == 0) { 68 intentInfo.setNonLocalizedLabel(v.coerceToString()); 69 } 70 } 71 72 if (ParsingPackageUtils.sUseRoundIcon) { 73 intentInfo.setIcon(sa.getResourceId( 74 R.styleable.AndroidManifestIntentFilter_roundIcon, 0)); 75 } 76 77 if (intentInfo.getIcon() == 0) { 78 intentInfo.setIcon( 79 sa.getResourceId(R.styleable.AndroidManifestIntentFilter_icon, 0)); 80 } 81 82 if (allowAutoVerify) { 83 intentFilter.setAutoVerify(sa.getBoolean( 84 R.styleable.AndroidManifestIntentFilter_autoVerify, 85 false)); 86 } 87 } finally { 88 sa.recycle(); 89 } 90 final int depth = parser.getDepth(); 91 int type; 92 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 93 && (type != XmlPullParser.END_TAG 94 || parser.getDepth() > depth)) { 95 if (type != XmlPullParser.START_TAG) { 96 continue; 97 } 98 99 final ParseResult result; 100 String nodeName = parser.getName(); 101 switch (nodeName) { 102 case "action": { 103 String value = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "name"); 104 if (value == null) { 105 result = input.error("No value supplied for <android:name>"); 106 } else if (value.isEmpty()) { 107 intentFilter.addAction(value); 108 // Prior to R, this was not a failure 109 result = input.deferError("No value supplied for <android:name>", 110 ParseInput.DeferredError.EMPTY_INTENT_ACTION_CATEGORY); 111 } else { 112 intentFilter.addAction(value); 113 result = input.success(null); 114 } 115 break; 116 } 117 case "category": { 118 String value = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "name"); 119 if (value == null) { 120 result = input.error("No value supplied for <android:name>"); 121 } else if (value.isEmpty()) { 122 intentFilter.addCategory(value); 123 // Prior to R, this was not a failure 124 result = input.deferError("No value supplied for <android:name>", 125 ParseInput.DeferredError.EMPTY_INTENT_ACTION_CATEGORY); 126 } else { 127 intentFilter.addCategory(value); 128 result = input.success(null); 129 } 130 break; 131 } 132 case "data": 133 result = parseData(intentInfo, res, parser, allowGlobs, input); 134 break; 135 default: 136 result = ParsingUtils.unknownTag("<intent-filter>", pkg, parser, input); 137 break; 138 } 139 140 if (result.isError()) { 141 return input.error(result); 142 } 143 } 144 145 intentInfo.setHasDefault(intentFilter.hasCategory(Intent.CATEGORY_DEFAULT)); 146 147 if (DEBUG) { 148 final StringBuilder cats = new StringBuilder("Intent d="); 149 cats.append(intentInfo.isHasDefault()); 150 cats.append(", cat="); 151 152 final Iterator<String> it = intentFilter.categoriesIterator(); 153 if (it != null) { 154 while (it.hasNext()) { 155 cats.append(' '); 156 cats.append(it.next()); 157 } 158 } 159 Slog.d(TAG, cats.toString()); 160 } 161 162 return input.success(intentInfo); 163 } 164 165 @NonNull parseData(ParsedIntentInfo intentInfo, Resources resources, XmlResourceParser parser, boolean allowGlobs, ParseInput input)166 private static ParseResult<ParsedIntentInfo> parseData(ParsedIntentInfo intentInfo, 167 Resources resources, XmlResourceParser parser, boolean allowGlobs, ParseInput input) { 168 IntentFilter intentFilter = intentInfo.getIntentFilter(); 169 TypedArray sa = resources.obtainAttributes(parser, R.styleable.AndroidManifestData); 170 try { 171 String str = sa.getNonConfigurationString( 172 R.styleable.AndroidManifestData_mimeType, 0); 173 if (str != null) { 174 try { 175 intentFilter.addDataType(str); 176 } catch (IntentFilter.MalformedMimeTypeException e) { 177 return input.error(e.toString()); 178 } 179 } 180 181 str = sa.getNonConfigurationString( 182 R.styleable.AndroidManifestData_mimeGroup, 0); 183 if (str != null) { 184 intentFilter.addMimeGroup(str); 185 } 186 187 str = sa.getNonConfigurationString( 188 R.styleable.AndroidManifestData_scheme, 0); 189 if (str != null) { 190 intentFilter.addDataScheme(str); 191 } 192 193 str = sa.getNonConfigurationString( 194 R.styleable.AndroidManifestData_ssp, 0); 195 if (str != null) { 196 intentFilter.addDataSchemeSpecificPart(str, 197 PatternMatcher.PATTERN_LITERAL); 198 } 199 200 str = sa.getNonConfigurationString( 201 R.styleable.AndroidManifestData_sspPrefix, 0); 202 if (str != null) { 203 intentFilter.addDataSchemeSpecificPart(str, 204 PatternMatcher.PATTERN_PREFIX); 205 } 206 207 str = sa.getNonConfigurationString( 208 R.styleable.AndroidManifestData_sspPattern, 0); 209 if (str != null) { 210 if (!allowGlobs) { 211 return input.error( 212 "sspPattern not allowed here; ssp must be literal"); 213 } 214 intentFilter.addDataSchemeSpecificPart(str, 215 PatternMatcher.PATTERN_SIMPLE_GLOB); 216 } 217 218 str = sa.getNonConfigurationString( 219 R.styleable.AndroidManifestData_sspAdvancedPattern, 0); 220 if (str != null) { 221 if (!allowGlobs) { 222 return input.error( 223 "sspAdvancedPattern not allowed here; ssp must be literal"); 224 } 225 intentFilter.addDataSchemeSpecificPart(str, 226 PatternMatcher.PATTERN_ADVANCED_GLOB); 227 } 228 229 str = sa.getNonConfigurationString( 230 R.styleable.AndroidManifestData_sspSuffix, 0); 231 if (str != null) { 232 intentFilter.addDataSchemeSpecificPart(str, 233 PatternMatcher.PATTERN_SUFFIX); 234 } 235 236 237 String host = sa.getNonConfigurationString( 238 R.styleable.AndroidManifestData_host, 0); 239 String port = sa.getNonConfigurationString( 240 R.styleable.AndroidManifestData_port, 0); 241 if (host != null) { 242 intentFilter.addDataAuthority(host, port); 243 } 244 245 str = sa.getNonConfigurationString( 246 R.styleable.AndroidManifestData_path, 0); 247 if (str != null) { 248 intentFilter.addDataPath(str, PatternMatcher.PATTERN_LITERAL); 249 } 250 251 str = sa.getNonConfigurationString( 252 R.styleable.AndroidManifestData_pathPrefix, 0); 253 if (str != null) { 254 intentFilter.addDataPath(str, PatternMatcher.PATTERN_PREFIX); 255 } 256 257 str = sa.getNonConfigurationString( 258 R.styleable.AndroidManifestData_pathPattern, 0); 259 if (str != null) { 260 if (!allowGlobs) { 261 return input.error( 262 "pathPattern not allowed here; path must be literal"); 263 } 264 intentFilter.addDataPath(str, PatternMatcher.PATTERN_SIMPLE_GLOB); 265 } 266 267 str = sa.getNonConfigurationString( 268 R.styleable.AndroidManifestData_pathAdvancedPattern, 0); 269 if (str != null) { 270 if (!allowGlobs) { 271 return input.error( 272 "pathAdvancedPattern not allowed here; path must be literal"); 273 } 274 intentFilter.addDataPath(str, PatternMatcher.PATTERN_ADVANCED_GLOB); 275 } 276 277 str = sa.getNonConfigurationString( 278 R.styleable.AndroidManifestData_pathSuffix, 0); 279 if (str != null) { 280 intentFilter.addDataPath(str, PatternMatcher.PATTERN_SUFFIX); 281 } 282 283 284 return input.success(null); 285 } finally { 286 sa.recycle(); 287 } 288 } 289 } 290