1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.LOLLIPOP; 4 import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; 5 6 import android.content.pm.PackageInfo; 7 import android.content.pm.PackageParser; 8 import android.content.pm.PackageParser.Callback; 9 import android.content.pm.PackageParser.Package; 10 import android.os.Build; 11 import android.util.ArraySet; 12 import android.util.DisplayMetrics; 13 import java.io.File; 14 import java.nio.file.Path; 15 import java.util.HashSet; 16 import java.util.List; 17 import java.util.Set; 18 import org.robolectric.RuntimeEnvironment; 19 import org.robolectric.annotation.Implements; 20 import org.robolectric.shadows.ShadowLog.LogItem; 21 import org.robolectric.util.ReflectionHelpers; 22 import org.robolectric.util.reflector.Accessor; 23 import org.robolectric.util.reflector.ForType; 24 import org.robolectric.util.reflector.Static; 25 import org.robolectric.util.reflector.WithType; 26 27 @Implements(value = PackageParser.class, isInAndroidSdk = false) 28 @SuppressWarnings("NewApi") 29 public class ShadowPackageParser { 30 31 /** Parses an AndroidManifest.xml file using the framework PackageParser. */ callParsePackage(Path apkFile)32 public static Package callParsePackage(Path apkFile) { 33 PackageParser packageParser = new PackageParser(); 34 35 try { 36 // TODO(christianw/brettchabot): workaround for NPE from probable bug in Q. 37 // Can be removed when upstream properly handles a null callback 38 // PackageParser#setMinAspectRatio(Package) 39 if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.Q) { 40 QHelper.setCallback(packageParser); 41 } 42 Package thePackage = packageParser.parsePackage(apkFile.toFile(), 0); 43 44 if (thePackage == null) { 45 List<LogItem> logItems = ShadowLog.getLogsForTag("PackageParser"); 46 if (logItems.isEmpty()) { 47 throw new RuntimeException("Failed to parse package " + apkFile); 48 } else { 49 LogItem logItem = logItems.get(0); 50 throw new RuntimeException( 51 "Failed to parse package " + apkFile + ": " + logItem.msg, logItem.throwable); 52 } 53 } 54 55 return thePackage; 56 } catch (Exception e) { 57 throw new RuntimeException(e); 58 } 59 } 60 61 /** Prevents ClassNotFoundError for Callback on pre-26. */ 62 private static class QHelper { setCallback(PackageParser packageParser)63 private static void setCallback(PackageParser packageParser) { 64 // TODO(christianw): this should be a CallbackImpl with the ApplicationPackageManager... 65 66 packageParser.setCallback( 67 new Callback() { 68 @Override 69 public boolean hasFeature(String s) { 70 return false; 71 } 72 73 // @Override for SDK < 30 74 public String[] getOverlayPaths(String s, String s1) { 75 return null; 76 } 77 78 // @Override for SDK < 30 79 public String[] getOverlayApks(String s) { 80 return null; 81 } 82 }); 83 } 84 } 85 86 /** Accessor interface for {@link PackageParser}'s internals. */ 87 @ForType(PackageParser.class) 88 interface _PackageParser_ { 89 90 // <= LOLLIPOP 91 @Static generatePackageInfo( PackageParser.Package p, int[] gids, int flags, long firstInstallTime, long lastUpdateTime, HashSet<String> grantedPermissions, @WithType("android.content.pm.PackageUserState") Object state)92 PackageInfo generatePackageInfo( 93 PackageParser.Package p, 94 int[] gids, 95 int flags, 96 long firstInstallTime, 97 long lastUpdateTime, 98 HashSet<String> grantedPermissions, 99 @WithType("android.content.pm.PackageUserState") Object state); 100 101 // LOLLIPOP_MR1 102 @Static generatePackageInfo( PackageParser.Package p, int[] gids, int flags, long firstInstallTime, long lastUpdateTime, ArraySet<String> grantedPermissions, @WithType("android.content.pm.PackageUserState") Object state)103 PackageInfo generatePackageInfo( 104 PackageParser.Package p, 105 int[] gids, 106 int flags, 107 long firstInstallTime, 108 long lastUpdateTime, 109 ArraySet<String> grantedPermissions, 110 @WithType("android.content.pm.PackageUserState") Object state); 111 112 @Static generatePackageInfo( PackageParser.Package p, int[] gids, int flags, long firstInstallTime, long lastUpdateTime, Set<String> grantedPermissions, @WithType("android.content.pm.PackageUserState") Object state)113 PackageInfo generatePackageInfo( 114 PackageParser.Package p, 115 int[] gids, 116 int flags, 117 long firstInstallTime, 118 long lastUpdateTime, 119 Set<String> grantedPermissions, 120 @WithType("android.content.pm.PackageUserState") Object state); 121 generatePackageInfo( PackageParser.Package p, int[] gids, int flags, long firstInstallTime, long lastUpdateTime)122 default PackageInfo generatePackageInfo( 123 PackageParser.Package p, 124 int[] gids, 125 int flags, 126 long firstInstallTime, 127 long lastUpdateTime) { 128 int apiLevel = RuntimeEnvironment.getApiLevel(); 129 130 if (apiLevel == LOLLIPOP) { 131 return generatePackageInfo( 132 p, 133 gids, 134 flags, 135 firstInstallTime, 136 lastUpdateTime, 137 new HashSet<>(), 138 newPackageUserState()); 139 } else if (apiLevel <= LOLLIPOP_MR1) { 140 return generatePackageInfo( 141 p, 142 gids, 143 flags, 144 firstInstallTime, 145 lastUpdateTime, 146 new ArraySet<>(), 147 newPackageUserState()); 148 } else { 149 return generatePackageInfo( 150 p, 151 gids, 152 flags, 153 firstInstallTime, 154 lastUpdateTime, 155 (Set<String>) new HashSet<String>(), 156 newPackageUserState()); 157 } 158 } 159 parsePackage(File file, String fileName, DisplayMetrics displayMetrics, int flags)160 Package parsePackage(File file, String fileName, DisplayMetrics displayMetrics, int flags); 161 } 162 newPackageUserState()163 private static Object newPackageUserState() { 164 try { 165 return ReflectionHelpers.newInstance(Class.forName("android.content.pm.PackageUserState")); 166 } catch (ClassNotFoundException e) { 167 throw new AssertionError(e); 168 } 169 } 170 171 /** Accessor interface for {@link Package}'s internals. */ 172 @ForType(Package.class) 173 public interface _Package_ { 174 175 @Accessor("mPath") getPath()176 String getPath(); 177 } 178 } 179