1 package org.robolectric.shadows; 2 3 import static android.content.pm.ApplicationInfo.FLAG_ALLOW_BACKUP; 4 import static android.content.pm.ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA; 5 import static android.content.pm.ApplicationInfo.FLAG_ALLOW_TASK_REPARENTING; 6 import static android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE; 7 import static android.content.pm.ApplicationInfo.FLAG_HAS_CODE; 8 import static android.content.pm.ApplicationInfo.FLAG_KILL_AFTER_RESTORE; 9 import static android.content.pm.ApplicationInfo.FLAG_PERSISTENT; 10 import static android.content.pm.ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS; 11 import static android.content.pm.ApplicationInfo.FLAG_RESTORE_ANY_VERSION; 12 import static android.content.pm.ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS; 13 import static android.content.pm.ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS; 14 import static android.content.pm.ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES; 15 import static android.content.pm.ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS; 16 import static android.content.pm.ApplicationInfo.FLAG_TEST_ONLY; 17 import static android.content.pm.ApplicationInfo.FLAG_VM_SAFE_MODE; 18 import static android.os.PatternMatcher.PATTERN_LITERAL; 19 import static android.os.PatternMatcher.PATTERN_PREFIX; 20 import static android.os.PatternMatcher.PATTERN_SIMPLE_GLOB; 21 import static java.util.Arrays.asList; 22 23 import android.content.IntentFilter.MalformedMimeTypeException; 24 import android.content.pm.ActivityInfo; 25 import android.content.pm.ComponentInfo; 26 import android.content.pm.PackageInfo; 27 import android.content.pm.PackageItemInfo; 28 import android.content.pm.PackageParser; 29 import android.content.pm.PackageParser.Activity; 30 import android.content.pm.PackageParser.ActivityIntentInfo; 31 import android.content.pm.PackageParser.IntentInfo; 32 import android.content.pm.PackageParser.Package; 33 import android.content.pm.PackageParser.Permission; 34 import android.content.pm.PackageParser.PermissionGroup; 35 import android.content.pm.PackageParser.Service; 36 import android.content.pm.PackageParser.ServiceIntentInfo; 37 import android.content.pm.PathPermission; 38 import android.content.pm.PermissionGroupInfo; 39 import android.content.pm.PermissionInfo; 40 import android.content.pm.ProviderInfo; 41 import android.content.pm.ServiceInfo; 42 import android.os.Build; 43 import android.os.Build.VERSION_CODES; 44 import android.os.Bundle; 45 import android.os.Process; 46 import android.util.Pair; 47 import com.google.common.base.Strings; 48 import java.util.ArrayList; 49 import java.util.Arrays; 50 import java.util.List; 51 import java.util.Map; 52 import org.robolectric.RuntimeEnvironment; 53 import org.robolectric.manifest.ActivityData; 54 import org.robolectric.manifest.AndroidManifest; 55 import org.robolectric.manifest.BroadcastReceiverData; 56 import org.robolectric.manifest.ContentProviderData; 57 import org.robolectric.manifest.IntentFilterData; 58 import org.robolectric.manifest.IntentFilterData.DataAuthority; 59 import org.robolectric.manifest.PackageItemData; 60 import org.robolectric.manifest.PathPermissionData; 61 import org.robolectric.manifest.PermissionGroupItemData; 62 import org.robolectric.manifest.PermissionItemData; 63 import org.robolectric.manifest.ServiceData; 64 import org.robolectric.res.AttributeResource; 65 import org.robolectric.res.ResName; 66 import org.robolectric.util.ReflectionHelpers; 67 68 /** Creates a {@link PackageInfo} from a {@link AndroidManifest} */ 69 public class LegacyManifestParser { 70 71 private static final List<Pair<String, Integer>> APPLICATION_FLAGS = 72 asList( 73 Pair.create("android:allowBackup", FLAG_ALLOW_BACKUP), 74 Pair.create("android:allowClearUserData", FLAG_ALLOW_CLEAR_USER_DATA), 75 Pair.create("android:allowTaskReparenting", FLAG_ALLOW_TASK_REPARENTING), 76 Pair.create("android:debuggable", FLAG_DEBUGGABLE), 77 Pair.create("android:hasCode", FLAG_HAS_CODE), 78 Pair.create("android:killAfterRestore", FLAG_KILL_AFTER_RESTORE), 79 Pair.create("android:persistent", FLAG_PERSISTENT), 80 Pair.create("android:resizeable", FLAG_RESIZEABLE_FOR_SCREENS), 81 Pair.create("android:restoreAnyVersion", FLAG_RESTORE_ANY_VERSION), 82 Pair.create("android:largeScreens", FLAG_SUPPORTS_LARGE_SCREENS), 83 Pair.create("android:normalScreens", FLAG_SUPPORTS_NORMAL_SCREENS), 84 Pair.create("android:anyDensity", FLAG_SUPPORTS_SCREEN_DENSITIES), 85 Pair.create("android:smallScreens", FLAG_SUPPORTS_SMALL_SCREENS), 86 Pair.create("android:testOnly", FLAG_TEST_ONLY), 87 Pair.create("android:vmSafeMode", FLAG_VM_SAFE_MODE)); 88 private static final List<Pair<String, Integer>> CONFIG_OPTIONS = 89 asList( 90 Pair.create("mcc", ActivityInfo.CONFIG_MCC), 91 Pair.create("mnc", ActivityInfo.CONFIG_MNC), 92 Pair.create("locale", ActivityInfo.CONFIG_LOCALE), 93 Pair.create("touchscreen", ActivityInfo.CONFIG_TOUCHSCREEN), 94 Pair.create("keyboard", ActivityInfo.CONFIG_KEYBOARD), 95 Pair.create("keyboardHidden", ActivityInfo.CONFIG_KEYBOARD_HIDDEN), 96 Pair.create("navigation", ActivityInfo.CONFIG_NAVIGATION), 97 Pair.create("screenLayout", ActivityInfo.CONFIG_SCREEN_LAYOUT), 98 Pair.create("fontScale", ActivityInfo.CONFIG_FONT_SCALE), 99 Pair.create("uiMode", ActivityInfo.CONFIG_UI_MODE), 100 Pair.create("orientation", ActivityInfo.CONFIG_ORIENTATION), 101 Pair.create("screenSize", ActivityInfo.CONFIG_SCREEN_SIZE), 102 Pair.create("smallestScreenSize", ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE)); 103 104 public static Package createPackage(AndroidManifest androidManifest) { 105 106 Package pkg = new Package(androidManifest.getPackageName()); 107 108 pkg.mVersionName = androidManifest.getVersionName(); 109 pkg.mVersionCode = androidManifest.getVersionCode(); 110 111 Map<String, PermissionItemData> permissionItemData = androidManifest.getPermissions(); 112 for (PermissionItemData itemData : permissionItemData.values()) { 113 Permission permission = new Permission(pkg, createPermissionInfo(pkg, itemData)); 114 permission.metaData = permission.info.metaData; 115 pkg.permissions.add(permission); 116 } 117 118 Map<String, PermissionGroupItemData> permissionGroupItemData = androidManifest.getPermissionGroups(); 119 for (PermissionGroupItemData itemData : permissionGroupItemData.values()) { 120 PermissionGroup permissionGroup = new PermissionGroup(pkg, createPermissionGroupInfo(pkg, itemData)); 121 permissionGroup.metaData = permissionGroup.info.metaData; 122 pkg.permissionGroups.add(permissionGroup); 123 } 124 125 pkg.requestedPermissions.addAll(androidManifest.getUsedPermissions()); 126 if (RuntimeEnvironment.getApiLevel() < VERSION_CODES.M) { 127 List<Boolean> permissionsRequired = 128 ReflectionHelpers.getField(pkg, "requestedPermissionsRequired"); 129 permissionsRequired.addAll(buildBooleanList(pkg.requestedPermissions.size(), true)); 130 } 131 132 pkg.applicationInfo.flags = decodeFlags(androidManifest.getApplicationAttributes()); 133 pkg.applicationInfo.targetSdkVersion = androidManifest.getTargetSdkVersion(); 134 pkg.applicationInfo.packageName = androidManifest.getPackageName(); 135 pkg.applicationInfo.processName = androidManifest.getProcessName(); 136 if (!Strings.isNullOrEmpty(androidManifest.getApplicationName())) { 137 pkg.applicationInfo.className = 138 buildClassName(pkg.applicationInfo.packageName, androidManifest.getApplicationName()); 139 if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.N_MR1) { 140 pkg.applicationInfo.name = pkg.applicationInfo.className; 141 } 142 } 143 pkg.applicationInfo.metaData = metaDataToBundle(androidManifest.getApplicationMetaData()); 144 pkg.applicationInfo.uid = Process.myUid(); 145 if (androidManifest.getThemeRef() != null) { 146 pkg.applicationInfo.theme = 147 RuntimeEnvironment.getAppResourceTable() 148 .getResourceId( 149 ResName.qualifyResName( 150 androidManifest.getThemeRef().replace("@", ""), pkg.packageName, "style")); 151 } 152 153 int labelRes = 0; 154 if (androidManifest.getLabelRef() != null) { 155 String fullyQualifiedName = 156 ResName.qualifyResName(androidManifest.getLabelRef(), androidManifest.getPackageName()); 157 Integer id = 158 fullyQualifiedName == null 159 ? null 160 : RuntimeEnvironment.getAppResourceTable() 161 .getResourceId(new ResName(fullyQualifiedName)); 162 labelRes = id != null ? id : 0; 163 } 164 165 pkg.applicationInfo.labelRes = labelRes; 166 String labelRef = androidManifest.getLabelRef(); 167 if (labelRef != null && !labelRef.startsWith("@")) { 168 pkg.applicationInfo.nonLocalizedLabel = labelRef; 169 } 170 171 Map<String, ActivityData> activityDatas = androidManifest.getActivityDatas(); 172 for (ActivityData data : activityDatas.values()) { 173 ActivityInfo activityInfo = new ActivityInfo(); 174 activityInfo.name = buildClassName(pkg.packageName, data.getName()); 175 activityInfo.packageName = pkg.packageName; 176 activityInfo.configChanges = getConfigChanges(data); 177 activityInfo.parentActivityName = data.getParentActivityName(); 178 activityInfo.metaData = metaDataToBundle(data.getMetaData().getValueMap()); 179 activityInfo.applicationInfo = pkg.applicationInfo; 180 activityInfo.targetActivity = data.getTargetActivityName(); 181 activityInfo.exported = data.isExported(); 182 activityInfo.permission = data.getPermission(); 183 String themeRef; 184 185 // Based on ShadowActivity 186 if (data.getThemeRef() != null) { 187 themeRef = data.getThemeRef(); 188 } else { 189 themeRef = androidManifest.getThemeRef(); 190 } 191 if (themeRef != null) { 192 activityInfo.theme = 193 RuntimeEnvironment.getAppResourceTable() 194 .getResourceId( 195 ResName.qualifyResName(themeRef.replace("@", ""), pkg.packageName, "style")); 196 } 197 198 if (data.getLabel() != null) { 199 activityInfo.labelRes = 200 RuntimeEnvironment.getAppResourceTable() 201 .getResourceId( 202 ResName.qualifyResName( 203 data.getLabel().replace("@", ""), pkg.packageName, "string")); 204 if (activityInfo.labelRes == 0) { 205 activityInfo.nonLocalizedLabel = data.getLabel(); 206 } 207 } 208 209 Activity activity = createActivity(pkg, activityInfo); 210 for (IntentFilterData intentFilterData : data.getIntentFilters()) { 211 ActivityIntentInfo outInfo = new ActivityIntentInfo(activity); 212 populateIntentInfo(intentFilterData, outInfo); 213 activity.intents.add(outInfo); 214 } 215 pkg.activities.add(activity); 216 } 217 218 for (ContentProviderData data : androidManifest.getContentProviders()) { 219 ProviderInfo info = new ProviderInfo(); 220 populateComponentInfo(info, pkg, data); 221 info.authority = data.getAuthorities(); 222 223 List<PathPermission> permissions = new ArrayList<>(); 224 for (PathPermissionData permissionData : data.getPathPermissionDatas()) { 225 permissions.add(createPathPermission(permissionData)); 226 } 227 info.pathPermissions = permissions.toArray(new PathPermission[permissions.size()]); 228 info.readPermission = data.getReadPermission(); 229 info.writePermission = data.getWritePermission(); 230 info.grantUriPermissions = data.getGrantUriPermissions(); 231 pkg.providers.add(createProvider(pkg, info)); 232 } 233 234 for (BroadcastReceiverData data : androidManifest.getBroadcastReceivers()) { 235 ActivityInfo info = new ActivityInfo(); 236 populateComponentInfo(info, pkg, data); 237 info.permission = data.getPermission(); 238 info.exported = data.isExported(); 239 240 Activity receiver = createActivity(pkg, info); 241 for (IntentFilterData intentFilterData : data.getIntentFilters()) { 242 ActivityIntentInfo outInfo = new ActivityIntentInfo(receiver); 243 populateIntentInfo(intentFilterData, outInfo); 244 receiver.intents.add(outInfo); 245 } 246 pkg.receivers.add(receiver); 247 } 248 249 for (ServiceData data : androidManifest.getServices()) { 250 ServiceInfo info = new ServiceInfo(); 251 populateComponentInfo(info, pkg, data); 252 info.permission = data.getPermission(); 253 info.exported = data.isExported(); 254 255 Service service = createService(pkg, info); 256 for (IntentFilterData intentFilterData : data.getIntentFilters()) { 257 ServiceIntentInfo outInfo = new ServiceIntentInfo(service); 258 populateIntentInfo(intentFilterData, outInfo); 259 service.intents.add(outInfo); 260 } 261 pkg.services.add(service); 262 } 263 264 return pkg; 265 } 266 267 private static PathPermission createPathPermission(PathPermissionData data) { 268 if (!Strings.isNullOrEmpty(data.pathPattern)) { 269 return new PathPermission( 270 data.pathPattern, PATTERN_SIMPLE_GLOB, data.readPermission, data.writePermission); 271 } else if (!Strings.isNullOrEmpty(data.path)) { 272 return new PathPermission( 273 data.path, PATTERN_LITERAL, data.readPermission, data.writePermission); 274 } else if (!Strings.isNullOrEmpty(data.pathPrefix)) { 275 return new PathPermission( 276 data.pathPrefix, PATTERN_PREFIX, data.readPermission, data.writePermission); 277 } else { 278 throw new IllegalStateException("Permission without type"); 279 } 280 } 281 282 private static void populateComponentInfo( 283 ComponentInfo outInfo, Package owner, PackageItemData itemData) { 284 populatePackageItemInfo(outInfo, owner, itemData); 285 outInfo.applicationInfo = owner.applicationInfo; 286 } 287 288 private static void populatePackageItemInfo( 289 PackageItemInfo outInfo, Package owner, PackageItemData itemData) { 290 outInfo.name = buildClassName(owner.packageName, itemData.getName()); 291 outInfo.packageName = owner.packageName; 292 outInfo.metaData = metaDataToBundle(itemData.getMetaData().getValueMap()); 293 } 294 295 private static List<Boolean> buildBooleanList(int size, boolean defaultVal) { 296 Boolean[] barray = new Boolean[size]; 297 Arrays.fill(barray, defaultVal); 298 return Arrays.asList(barray); 299 } 300 301 private static PackageParser.Provider createProvider(Package pkg, ProviderInfo info) { 302 PackageParser.Provider provider = 303 ReflectionHelpers.callConstructor(PackageParser.Provider.class); 304 populateComponent(pkg, info, provider); 305 return provider; 306 } 307 308 private static Activity createActivity(Package pkg, ActivityInfo activityInfo) { 309 Activity activity = ReflectionHelpers.callConstructor(Activity.class); 310 populateComponent(pkg, activityInfo, activity); 311 return activity; 312 } 313 314 private static Service createService(Package pkg, ServiceInfo info) { 315 PackageParser.Service service = ReflectionHelpers.callConstructor(PackageParser.Service.class); 316 populateComponent(pkg, info, service); 317 return service; 318 } 319 320 private static void populateComponent( 321 Package pkg, ComponentInfo info, PackageParser.Component component) { 322 ReflectionHelpers.setField(component, "info", info); 323 ReflectionHelpers.setField(component, "intents", new ArrayList<>()); 324 ReflectionHelpers.setField(component, "owner", pkg); 325 ReflectionHelpers.setField(component, "className", info.name); 326 } 327 328 private static void populateIntentInfo(IntentFilterData intentFilterData, IntentInfo outInfo) { 329 for (String action : intentFilterData.getActions()) { 330 outInfo.addAction(action); 331 } 332 for (String category : intentFilterData.getCategories()) { 333 outInfo.addCategory(category); 334 } 335 for (DataAuthority dataAuthority : intentFilterData.getAuthorities()) { 336 outInfo.addDataAuthority(dataAuthority.getHost(), dataAuthority.getPort()); 337 } 338 for (String mimeType : intentFilterData.getMimeTypes()) { 339 try { 340 outInfo.addDataType(mimeType); 341 } catch (MalformedMimeTypeException e) { 342 throw new RuntimeException(e); 343 } 344 } 345 for (String scheme : intentFilterData.getSchemes()) { 346 outInfo.addDataScheme(scheme); 347 } 348 for (String pathPattern : intentFilterData.getPathPatterns()) { 349 outInfo.addDataPath(pathPattern, PATTERN_SIMPLE_GLOB); 350 } 351 for (String pathPattern : intentFilterData.getPathPrefixes()) { 352 outInfo.addDataPath(pathPattern, PATTERN_PREFIX); 353 } 354 for (String pathPattern : intentFilterData.getPaths()) { 355 outInfo.addDataPath(pathPattern, PATTERN_LITERAL); 356 } 357 } 358 359 private static int getConfigChanges(ActivityData activityData) { 360 String s = activityData.getConfigChanges(); 361 362 int res = 0; 363 364 // quick sanity check. 365 if (s == null || "".equals(s)) { 366 return res; 367 } 368 369 String[] pieces = s.split("\\|"); 370 371 for (String s1 : pieces) { 372 s1 = s1.trim(); 373 374 for (Pair<String, Integer> pair : CONFIG_OPTIONS) { 375 if (s1.equals(pair.first)) { 376 res |= pair.second; 377 break; 378 } 379 } 380 } 381 382 // Matches platform behavior 383 if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.O) { 384 res |= ActivityInfo.CONFIG_MNC; 385 res |= ActivityInfo.CONFIG_MCC; 386 } 387 388 return res; 389 } 390 391 private static int decodeFlags(Map<String, String> applicationAttributes) { 392 int applicationFlags = 0; 393 for (Pair<String, Integer> pair : APPLICATION_FLAGS) { 394 if ("true".equals(applicationAttributes.get(pair.first))) { 395 applicationFlags |= pair.second; 396 } 397 } 398 return applicationFlags; 399 } 400 401 private static PermissionInfo createPermissionInfo(Package owner, PermissionItemData itemData) { 402 PermissionInfo permissionInfo = new PermissionInfo(); 403 populatePackageItemInfo(permissionInfo, owner, itemData); 404 405 permissionInfo.group = itemData.getPermissionGroup(); 406 permissionInfo.protectionLevel = decodeProtectionLevel(itemData.getProtectionLevel()); 407 permissionInfo.metaData = metaDataToBundle(itemData.getMetaData().getValueMap()); 408 409 String descriptionRef = itemData.getDescription(); 410 if (descriptionRef != null) { 411 ResName descResName = 412 AttributeResource.getResourceReference(descriptionRef, owner.packageName, "string"); 413 permissionInfo.descriptionRes = 414 RuntimeEnvironment.getAppResourceTable().getResourceId(descResName); 415 } 416 417 String labelRefOrString = itemData.getLabel(); 418 if (labelRefOrString != null) { 419 if (AttributeResource.isResourceReference(labelRefOrString)) { 420 ResName labelResName = 421 AttributeResource.getResourceReference(labelRefOrString, owner.packageName, "string"); 422 permissionInfo.labelRes = 423 RuntimeEnvironment.getAppResourceTable().getResourceId(labelResName); 424 } else { 425 permissionInfo.nonLocalizedLabel = labelRefOrString; 426 } 427 } 428 429 return permissionInfo; 430 } 431 432 private static PermissionGroupInfo createPermissionGroupInfo(Package owner, 433 PermissionGroupItemData itemData) { 434 PermissionGroupInfo permissionGroupInfo = new PermissionGroupInfo(); 435 populatePackageItemInfo(permissionGroupInfo, owner, itemData); 436 437 permissionGroupInfo.metaData = metaDataToBundle(itemData.getMetaData().getValueMap()); 438 439 String descriptionRef = itemData.getDescription(); 440 if (descriptionRef != null) { 441 ResName descResName = 442 AttributeResource.getResourceReference(descriptionRef, owner.packageName, "string"); 443 permissionGroupInfo.descriptionRes = 444 RuntimeEnvironment.getAppResourceTable().getResourceId(descResName); 445 } 446 447 String labelRefOrString = itemData.getLabel(); 448 if (labelRefOrString != null) { 449 if (AttributeResource.isResourceReference(labelRefOrString)) { 450 ResName labelResName = 451 AttributeResource.getResourceReference(labelRefOrString, owner.packageName, "string"); 452 permissionGroupInfo.labelRes = 453 RuntimeEnvironment.getAppResourceTable().getResourceId(labelResName); 454 } else { 455 permissionGroupInfo.nonLocalizedLabel = labelRefOrString; 456 } 457 } 458 459 return permissionGroupInfo; 460 } 461 462 private static int decodeProtectionLevel(String protectionLevel) { 463 if (protectionLevel == null) { 464 return PermissionInfo.PROTECTION_NORMAL; 465 } 466 467 switch (protectionLevel) { 468 case "normal": 469 return PermissionInfo.PROTECTION_NORMAL; 470 case "dangerous": 471 return PermissionInfo.PROTECTION_DANGEROUS; 472 case "signature": 473 return PermissionInfo.PROTECTION_SIGNATURE; 474 case "signatureOrSystem": 475 return PermissionInfo.PROTECTION_SIGNATURE_OR_SYSTEM; 476 default: 477 throw new IllegalArgumentException("unknown protection level " + protectionLevel); 478 } 479 } 480 481 /** 482 * Goes through the meta data and puts each value in to a bundle as the correct type. 483 * 484 * <p>Note that this will convert resource identifiers specified via the value attribute as well. 485 * 486 * @param meta Meta data to put in to a bundle 487 * @return bundle containing the meta data 488 */ 489 private static Bundle metaDataToBundle(Map<String, Object> meta) { 490 if (meta.size() == 0) { 491 return null; 492 } 493 494 Bundle bundle = new Bundle(); 495 496 for (Map.Entry<String, Object> entry : meta.entrySet()) { 497 if (Boolean.class.isInstance(entry.getValue())) { 498 bundle.putBoolean(entry.getKey(), (Boolean) entry.getValue()); 499 } else if (Float.class.isInstance(entry.getValue())) { 500 bundle.putFloat(entry.getKey(), (Float) entry.getValue()); 501 } else if (Integer.class.isInstance(entry.getValue())) { 502 bundle.putInt(entry.getKey(), (Integer) entry.getValue()); 503 } else { 504 bundle.putString(entry.getKey(), entry.getValue().toString()); 505 } 506 } 507 return bundle; 508 } 509 510 private static String buildClassName(String pkg, String cls) { 511 if (Strings.isNullOrEmpty(cls)) { 512 throw new IllegalArgumentException("Empty class name in package " + pkg); 513 } 514 char c = cls.charAt(0); 515 if (c == '.') { 516 return (pkg + cls).intern(); 517 } 518 if (cls.indexOf('.') < 0) { 519 StringBuilder b = new StringBuilder(pkg); 520 b.append('.'); 521 b.append(cls); 522 return b.toString(); 523 } 524 return cls; 525 // TODO: consider reenabling this for stricter platform-complaint checking 526 // if (c >= 'a' && c <= 'z') { 527 // return cls; 528 // } 529 // throw new IllegalArgumentException("Bad class name " + cls + " in package " + pkg); 530 } 531 } 532