• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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