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