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