1 package org.robolectric.shadows; 2 3 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK; 4 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION; 5 import static android.content.pm.PackageParser.PARSE_COLLECT_CERTIFICATES; 6 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; 7 import static org.robolectric.shadow.api.Shadow.directlyOn; 8 import static org.robolectric.util.ReflectionHelpers.ClassParameter.from; 9 10 import android.content.pm.PackageManager; 11 import android.content.pm.PackageParser; 12 import android.content.pm.PackageParser.Package; 13 import android.content.pm.PackageParser.ParseFlags; 14 import android.content.pm.PackageParser.SigningDetails; 15 import android.content.pm.Signature; 16 import android.content.res.AssetManager; 17 import android.content.res.Resources; 18 import android.content.res.TypedArray; 19 import android.content.res.XmlResourceParser; 20 import android.os.Build; 21 import android.os.Bundle; 22 import android.os.Trace; 23 import android.util.AttributeSet; 24 import android.util.DisplayMetrics; 25 import android.util.Slog; 26 import android.util.TypedValue; 27 import com.android.internal.util.XmlUtils; 28 import java.io.File; 29 import java.io.IOException; 30 import java.security.cert.Certificate; 31 import libcore.io.IoUtils; 32 import org.robolectric.RuntimeEnvironment; 33 import org.robolectric.annotation.Implementation; 34 import org.robolectric.annotation.Implements; 35 import org.robolectric.annotation.RealObject; 36 import org.robolectric.res.FsFile; 37 import org.robolectric.util.ReflectionHelpers; 38 import org.xmlpull.v1.XmlPullParser; 39 import org.xmlpull.v1.XmlPullParserException; 40 41 /** 42 * Shadow for {@link PackageParser} 43 */ 44 @Implements(value = PackageParser.class, isInAndroidSdk = false) 45 public class ShadowPackageParser { 46 47 private static final String TAG = "ShadowPackageParser"; 48 49 private int mParseError; 50 private String mArchiveSourcePath; 51 52 @RealObject PackageParser realObject; 53 private static String MANIFEST_FILE; 54 55 /** Parses an AndroidManifest.xml file using the framework PackageParser. */ 56 public static Package callParsePackage(FsFile manifestFile) { 57 MANIFEST_FILE = manifestFile.getPath(); 58 PackageParser packageParser = new PackageParser(); 59 60 int flags = PackageParser.PARSE_IGNORE_PROCESSES; 61 try { 62 if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.LOLLIPOP) { 63 return packageParser.parsePackage(new File(manifestFile.getPath()), flags); 64 } else { // JB -> KK 65 return ReflectionHelpers.callInstanceMethod( 66 PackageParser.class, 67 packageParser, 68 "parsePackage", 69 from(File.class, new File(manifestFile.getPath())), 70 from(String.class, manifestFile.getPath()), 71 from(DisplayMetrics.class, new DisplayMetrics()), 72 from(int.class, flags)); 73 } 74 } catch (Exception e) { 75 throw new RuntimeException(e); 76 } 77 } 78 79 /** 80 * We only need to implement this method because the framework expects the AndroidManifest.xml 81 * file to exist in the root of the apk. We can't set the field statically since its final and 82 * therefore inlined by the compiler. We should be able to remove this method and others below for 83 * other framework versions if we move to a model that mirrors what the framework expects, e.g: 84 * the AndroidManifest.xml at the root of the resources.ap_ 85 */ 86 @Implementation(minSdk = Build.VERSION_CODES.JELLY_BEAN, maxSdk = Build.VERSION_CODES.KITKAT) 87 public Package parsePackage( 88 File sourceFile, String destCodePath, DisplayMetrics metrics, int flags) { 89 mParseError = PackageManager.INSTALL_SUCCEEDED; 90 91 XmlResourceParser parser = null; 92 AssetManager assmgr = null; 93 Resources res = null; 94 boolean assetError = true; 95 try { 96 assmgr = new AssetManager(); 97 int cookie = mArchiveSourcePath != null ? assmgr.addAssetPath(mArchiveSourcePath) : 1; 98 if (cookie != 0) { 99 res = new Resources(assmgr, metrics, null); 100 parser = assmgr.openXmlResourceParser(cookie, MANIFEST_FILE); 101 assetError = false; 102 } else { 103 Slog.w(TAG, "Failed adding asset path:" + mArchiveSourcePath); 104 } 105 } catch (Exception e) { 106 Slog.w(TAG, "Unable to read AndroidManifest.xml of " + mArchiveSourcePath, e); 107 } 108 if (assetError) { 109 if (assmgr != null) { 110 assmgr.close(); 111 } 112 mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; 113 throw new RuntimeException("Failed to parse Manifest"); 114 } 115 String[] errorText = new String[1]; 116 Package pkg = null; 117 try { 118 pkg = 119 directlyOn( 120 realObject, 121 PackageParser.class, 122 "parsePackage", 123 ReflectionHelpers.ClassParameter.from(Resources.class, res), 124 ReflectionHelpers.ClassParameter.from(XmlResourceParser.class, parser), 125 ReflectionHelpers.ClassParameter.from(int.class, flags), 126 ReflectionHelpers.ClassParameter.from(String[].class, errorText)); 127 } catch (Exception e) { 128 mParseError = INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION; 129 throw new RuntimeException("Failed to parse Manifest", e); 130 } 131 132 if (pkg == null) { 133 parser.close(); 134 assmgr.close(); 135 throw new RuntimeException("Failed to parse Manifest" + errorText[0]); 136 } 137 138 parser.close(); 139 assmgr.close(); 140 141 if (RuntimeEnvironment.getApiLevel() <= Build.VERSION_CODES.O_MR1) { 142 ReflectionHelpers.setField(pkg, "mSignatures", null); 143 } else { 144 // BEGIN-INTERNAL 145 ReflectionHelpers.setField(pkg, "mSigningDetails", SigningDetails.UNKNOWN); 146 // END-INTERNAL 147 } 148 149 return pkg; 150 } 151 152 @Implementation 153 public Bundle parseMetaData( 154 Resources res, XmlPullParser parser, AttributeSet attrs, Bundle data, String[] outError) 155 throws XmlPullParserException, IOException { 156 157 TypedArray sa = 158 res.obtainAttributes(attrs, com.android.internal.R.styleable.AndroidManifestMetaData); 159 160 if (data == null) { 161 data = new Bundle(); 162 } 163 164 String name = 165 sa.getNonConfigurationString( 166 com.android.internal.R.styleable.AndroidManifestMetaData_name, 0); 167 if (name == null) { 168 outError[0] = "<meta-data> requires an android:name attribute"; 169 sa.recycle(); 170 return null; 171 } 172 173 name = name.intern(); 174 175 TypedValue v = sa.peekValue(com.android.internal.R.styleable.AndroidManifestMetaData_resource); 176 if (v != null && v.resourceId != 0) { 177 // Slog.i(TAG, "Meta data ref " + name + ": " + v); 178 data.putInt(name, v.resourceId); 179 } else { 180 v = sa.peekValue(com.android.internal.R.styleable.AndroidManifestMetaData_value); 181 // Slog.i(TAG, "Meta data " + name + ": " + v); 182 if (v != null) { 183 if (v.type == TypedValue.TYPE_STRING) { 184 CharSequence cs = v.coerceToString(); 185 data.putString(name, cs != null ? cs.toString().intern() : null); 186 } else if (v.type == TypedValue.TYPE_INT_BOOLEAN) { 187 data.putBoolean(name, v.data != 0); 188 } else if (v.type >= TypedValue.TYPE_FIRST_INT && v.type <= TypedValue.TYPE_LAST_INT) { 189 data.putInt(name, v.data); 190 } else if (v.type == TypedValue.TYPE_FLOAT) { 191 data.putFloat(name, v.getFloat()); 192 } else { 193 if (true) { 194 Slog.w( 195 TAG, 196 "<meta-data> only supports string, integer, float, color, boolean, and " 197 + "resource reference types: " 198 + parser.getName() 199 + " at " 200 + mArchiveSourcePath 201 + " " 202 + parser.getPositionDescription()); 203 } else { 204 outError[0] = 205 "<meta-data> only supports string, integer, float, color, boolean, and resource " 206 + "reference types"; 207 data = null; 208 } 209 } 210 } else { 211 outError[0] = "<meta-data> requires an android:value or android:resource attribute"; 212 data = null; 213 } 214 } 215 216 sa.recycle(); 217 218 XmlUtils.skipCurrentTag(parser); 219 220 return data; 221 } 222 223 /* 224 * Only required because framework expects AndroidManifest.xml at the root of the resources file. 225 * See comment on 226 * {@link #parsePackage(java.io.File, java.lang.String, android.util.DisplayMetrics, int)} 227 */ 228 @Implementation(minSdk = Build.VERSION_CODES.LOLLIPOP) 229 public Package parseBaseApk(File apkFile, AssetManager assets, int flags) { 230 final String apkPath = apkFile.getAbsolutePath(); 231 232 mParseError = PackageManager.INSTALL_SUCCEEDED; 233 mArchiveSourcePath = apkFile.getAbsolutePath(); 234 235 final int cookie = 0; 236 237 Resources res = null; 238 XmlResourceParser parser = null; 239 try { 240 res = new Resources(assets, new DisplayMetrics(), null); 241 242 parser = assets.openXmlResourceParser(cookie, MANIFEST_FILE); 243 final String[] outError = new String[1]; 244 final Package pkg; 245 if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.O) { 246 pkg = 247 directlyOn( 248 realObject, 249 PackageParser.class, 250 "parseBaseApk", 251 ReflectionHelpers.ClassParameter.from(String.class, apkFile.getAbsolutePath()), 252 ReflectionHelpers.ClassParameter.from(Resources.class, res), 253 ReflectionHelpers.ClassParameter.from(XmlResourceParser.class, parser), 254 ReflectionHelpers.ClassParameter.from(int.class, flags), 255 ReflectionHelpers.ClassParameter.from(String[].class, outError)); 256 } else { 257 pkg = 258 directlyOn( 259 realObject, 260 PackageParser.class, 261 "parseBaseApk", 262 ReflectionHelpers.ClassParameter.from(Resources.class, res), 263 ReflectionHelpers.ClassParameter.from(XmlResourceParser.class, parser), 264 ReflectionHelpers.ClassParameter.from(int.class, flags), 265 ReflectionHelpers.ClassParameter.from(String[].class, outError)); 266 } 267 268 if (pkg == null) { 269 throw new Exception( 270 "Parse error at " + parser.getPositionDescription() + "): " + outError[0]); 271 } 272 273 pkg.baseCodePath = apkPath; 274 275 if (RuntimeEnvironment.getApiLevel() <= Build.VERSION_CODES.O_MR1) { 276 ReflectionHelpers.setField(pkg, "mSignatures", null); 277 } else { 278 // BEGIN-INTERNAL 279 ReflectionHelpers.setField(pkg, "mSigningDetails", SigningDetails.UNKNOWN); 280 // END-INTERNAL 281 } 282 283 return pkg; 284 } catch (Exception e) { 285 throw new RuntimeException( 286 "Failed to read manifest from " 287 + apkPath 288 + "Error code: " 289 + INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, 290 e); 291 } finally { 292 IoUtils.closeQuietly(parser); 293 } 294 } 295 296 /* 297 * Only required because framework expects AndroidManifest.xml at the root of the resources file. 298 * See comment on 299 * {@link #parsePackage(java.io.File, java.lang.String, android.util.DisplayMetrics, int)} 300 */ 301 @Implementation(minSdk = Build.VERSION_CODES.N) 302 public static Object parseApkLite(File apkFile, @ParseFlags int flags) { 303 final String apkPath = apkFile.getAbsolutePath(); 304 305 AssetManager assets = null; 306 XmlResourceParser parser = null; 307 try { 308 assets = new AssetManager(); 309 310 int cookie = assets.addAssetPath(apkPath); 311 if (cookie == 0) { 312 throw new RuntimeException( 313 "Failed to parse " + apkPath + "Error code: " + INSTALL_PARSE_FAILED_NOT_APK); 314 } 315 316 final DisplayMetrics metrics = new DisplayMetrics(); 317 metrics.setToDefaults(); 318 319 final Resources res = new Resources(assets, metrics, null); 320 parser = assets.openXmlResourceParser(cookie, MANIFEST_FILE); 321 322 Signature[] signatures = null; 323 Certificate[][] certificates = null; 324 Object signatureDetails = null; 325 if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) { 326 // TODO: factor signature related items out of Package object 327 final Package tempPkg = ReflectionHelpers.newInstance(Package.class); 328 329 try { 330 directlyOn( 331 PackageParser.class, 332 "collectCertificates", 333 ReflectionHelpers.ClassParameter.from(Package.class, tempPkg), 334 ReflectionHelpers.ClassParameter.from(File.class, apkFile), 335 ReflectionHelpers.ClassParameter.from(int.class, 0 /*parseFlags*/)); 336 } finally { 337 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); 338 } 339 340 if (RuntimeEnvironment.getApiLevel() <= Build.VERSION_CODES.O_MR1) { 341 signatures = ReflectionHelpers.getField(tempPkg, "mSignatures"); 342 certificates = ReflectionHelpers.getField(tempPkg, "mCertificates"); 343 } else { 344 // BEGIN-INTERNAL 345 signatureDetails = tempPkg.mSigningDetails; 346 // END-INTERNAL 347 } 348 } 349 350 final AttributeSet attrs = parser; 351 352 // BEGIN-INTERNAL 353 if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.P) { 354 return directlyOn( 355 PackageParser.class, 356 "parseApkLite", 357 ReflectionHelpers.ClassParameter.from(String.class, apkPath), 358 ReflectionHelpers.ClassParameter.from(XmlPullParser.class, parser), 359 ReflectionHelpers.ClassParameter.from(AttributeSet.class, attrs), 360 ReflectionHelpers.ClassParameter.from(SigningDetails.class, signatureDetails)); 361 } else 362 // END-INTERNAL 363 if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.O_MR1) { 364 return directlyOn( 365 PackageParser.class, 366 "parseApkLite", 367 ReflectionHelpers.ClassParameter.from(String.class, apkPath), 368 ReflectionHelpers.ClassParameter.from(XmlPullParser.class, parser), 369 ReflectionHelpers.ClassParameter.from(AttributeSet.class, attrs), 370 ReflectionHelpers.ClassParameter.from(Signature[].class, signatures), 371 ReflectionHelpers.ClassParameter.from(Certificate[][].class, certificates)); 372 } else if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.O) { 373 return directlyOn( 374 PackageParser.class, 375 "parseApkLite", 376 ReflectionHelpers.ClassParameter.from(String.class, apkPath), 377 ReflectionHelpers.ClassParameter.from(XmlPullParser.class, parser), 378 ReflectionHelpers.ClassParameter.from(AttributeSet.class, attrs), 379 ReflectionHelpers.ClassParameter.from(int.class, flags), 380 ReflectionHelpers.ClassParameter.from(Signature[].class, signatures), 381 ReflectionHelpers.ClassParameter.from(Certificate[][].class, certificates)); 382 } else { 383 return directlyOn( 384 PackageParser.class, 385 "parseApkLite", 386 ReflectionHelpers.ClassParameter.from(String.class, apkPath), 387 ReflectionHelpers.ClassParameter.from(Resources.class, res), 388 ReflectionHelpers.ClassParameter.from(XmlPullParser.class, parser), 389 ReflectionHelpers.ClassParameter.from(AttributeSet.class, attrs), 390 ReflectionHelpers.ClassParameter.from(int.class, flags), 391 ReflectionHelpers.ClassParameter.from(Signature[].class, signatures), 392 ReflectionHelpers.ClassParameter.from(Certificate[][].class, certificates)); 393 } 394 395 } catch (Exception e) { 396 throw new RuntimeException( 397 "Failed to parse " 398 + apkPath 399 + "Error code: " 400 + INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION); 401 } finally { 402 IoUtils.closeQuietly(parser); 403 IoUtils.closeQuietly(assets); 404 } 405 } 406 } 407