Get minSdkVersion and targetSdkVersion from apk file

Ruben Roy picture Ruben Roy · Dec 4, 2013 · Viewed 10k times · Source

I am trying to get the values of minSdkVersion and targetSdkVersion from an apk stored on the device. Getting other details are discussed here, but only the targetSdkVersion is available in the ApplicationInfo class. Can the minSdkVersion be obtained other than by extracting the apk file and reading AndroidManifest.xml?

Answer

JohanShogun picture JohanShogun · Jul 23, 2015

I do not believe this is possible to do on your own and there is no pre-made api for this. The current methods that read and parse the AndroidManifest do not consider minSdkVersion at all.

In order to check your apk file without using the ready made functions you end up needing to add it manually to the asset manager. And that method is marked with "Not for use by applications" which in my experience usually means that it's not a good idea to call it from an application.

http://androidxref.com/5.1.1_r6/xref/frameworks/base/core/java/android/content/res/AssetManager.java#612

If you do manage to call:

public final int addAssetPath(String path) {

From your application you should be able to get the minSdkVersion by parsing the XML file, consider this code:

private static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";

....
method:

final int cookie = loadApkIntoAssetManager(assets, apkPath, flags);

Resources res = null;
XmlResourceParser parser = null;
try {
    res = new Resources(assets, mMetrics, null);
    assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            Build.VERSION.RESOURCES_SDK_INT);
    parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);

    final String[] outError = new String[1];
    final Package pkg = parseBaseApk(res, parser, flags, outError);
    if (pkg == null) {
        throw new PackageParserException(mParseError,
                apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]);
    }
}

Code: http://androidxref.com/5.1.1_r6/xref/frameworks/base/core/java/android/content/pm/PackageParser.java#863

Where you should be able to parse your AndroidManifest file using the XmlResourceParser and find the element for the minSdkVersion.

If you want to try it out yourself, just copy following static methods and call getMinSdkVersion(yourApkFile):

/**
 * Parses AndroidManifest of the given apkFile and returns the value of
 * minSdkVersion using undocumented API which is marked as
 * "not to be used by applications"
 * 
 * @param apkFile
 * @return minSdkVersion or -1 if not found in Manifest
 * @throws IOException
 * @throws XmlPullParserException
 */
public static int getMinSdkVersion(File apkFile) throws IOException,
        XmlPullParserException {

    XmlResourceParser parser = getParserForManifest(apkFile);
    while (parser.next() != XmlPullParser.END_DOCUMENT) {

        if (parser.getEventType() == XmlPullParser.START_TAG
                && parser.getName().equals("uses-sdk")) {
            for (int i = 0; i < parser.getAttributeCount(); i++) {
                if (parser.getAttributeName(i).equals("minSdkVersion")) {
                    return parser.getAttributeIntValue(i, -1);
                }
            }
        }
    }
    return -1;

}

/**
 * Tries to get the parser for the given apkFile from {@link AssetManager}
 * using undocumented API which is marked as
 * "not to be used by applications"
 * 
 * @param apkFile
 * @return
 * @throws IOException
 */
private static XmlResourceParser getParserForManifest(final File apkFile)
        throws IOException {
    final Object assetManagerInstance = getAssetManager();
    final int cookie = addAssets(apkFile, assetManagerInstance);
    return ((AssetManager) assetManagerInstance).openXmlResourceParser(
            cookie, "AndroidManifest.xml");
}

/**
 * Get the cookie of an asset using an undocumented API call that is marked
 * as "no to be used by applications" in its source code
 * 
 * @see <a
 *      href="http://androidxref.com/5.1.1_r6/xref/frameworks/base/core/java/android/content/res/AssetManager.java#612">AssetManager.java#612</a>
 * @return the cookie
 */
private static int addAssets(final File apkFile,
        final Object assetManagerInstance) {
    try {
        Method addAssetPath = assetManagerInstance.getClass().getMethod(
                "addAssetPath", new Class[] { String.class });
        return (Integer) addAssetPath.invoke(assetManagerInstance,
                apkFile.getAbsolutePath());
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    return -1;
}

/**
 * Get {@link AssetManager} using reflection
 * 
 * @return
 */
private static Object getAssetManager() {
    Class assetManagerClass = null;
    try {
        assetManagerClass = Class
                .forName("android.content.res.AssetManager");
        Object assetManagerInstance = assetManagerClass.newInstance();
        return assetManagerInstance;
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    return null;
}

You may need a reflection call to set this as well:

assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            Build.VERSION.RESOURCES_SDK_INT);

No guarantees that it will work (nor that it won't be bad for your phone) the operation should be safe since you're creating a new AssetManager and not relying on the AssetManager for your application. From a quick look in the C++ code it seems that it's not being added to any global list.

Code: http://androidxref.com/5.1.1_r6/xref/frameworks/base/libs/androidfw/AssetManager.cpp#173