constructor to create App from an installed APK

Using PackageManager and Context, this will create an App instance from
the APK specified by the packageName.
This commit is contained in:
Hans-Christoph Steiner 2014-05-06 16:24:05 -04:00
parent c1a6f545cf
commit e35a6e05c7
2 changed files with 156 additions and 139 deletions

View File

@ -1,14 +1,26 @@
package org.fdroid.fdroid.data; package org.fdroid.fdroid.data;
import android.annotation.TargetApi;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.pm.ApplicationInfo; import android.content.Context;
import android.content.pm.*;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.Cursor; import android.database.Cursor;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import org.fdroid.fdroid.AppFilter; import org.fdroid.fdroid.AppFilter;
import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.Utils;
import java.util.Date; import java.io.File;
import java.util.List; import java.io.IOException;
import java.io.InputStream;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class App extends ValueObject implements Comparable<App> { public class App extends ValueObject implements Comparable<App> {
@ -163,6 +175,134 @@ public class App extends ValueObject implements Comparable<App> {
} }
} }
/**
* Instantiate from a locally installed package.
*/
@TargetApi(9)
public App(Context context, PackageManager pm, String packageName)
throws CertificateEncodingException, IOException, NameNotFoundException {
ApplicationInfo appInfo;
PackageInfo packageInfo;
appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES
| PackageManager.GET_PERMISSIONS);
this.name = (String) appInfo.loadLabel(pm);
this.summary = (String) appInfo.loadDescription(pm);
this.id = appInfo.packageName;
if (Build.VERSION.SDK_INT > 8) {
this.added = new Date(packageInfo.firstInstallTime);
this.lastUpdated = new Date(packageInfo.lastUpdateTime);
} else {
this.added = new Date(System.currentTimeMillis());
this.lastUpdated = this.added;
}
this.appInfo = appInfo;
this.apks = new ArrayList<Apk>();
// TODO: use pm.getInstallerPackageName(packageName) for something
File apkFile = new File(appInfo.publicSourceDir);
Apk apk = new Apk();
apk.version = packageInfo.versionName;
apk.vercode = packageInfo.versionCode;
apk.hashType = "sha256";
apk.hash = Utils.getBinaryHash(apkFile, apk.hashType);
apk.added = this.added;
apk.minSdkVersion = Utils.getMinSdkVersion(context, packageName);
apk.id = this.id;
apk.installedFile = apkFile;
if (packageInfo.requestedPermissions == null)
apk.permissions = null;
else
apk.permissions = Utils.CommaSeparatedList.make(
Arrays.asList(packageInfo.requestedPermissions));
apk.apkName = apk.id + "_" + apk.vercode + ".apk";
FeatureInfo[] features = packageInfo.reqFeatures;
if (features != null && features.length > 0) {
List<String> featureNames = new ArrayList<String>(features.length);
for (int i = 0; i < features.length; i++)
featureNames.add(features[i].name);
apk.features = Utils.CommaSeparatedList.make(featureNames);
}
// Signature[] sigs = pkgInfo.signatures;
byte[] rawCertBytes;
JarFile apkJar = new JarFile(apkFile);
JarEntry aSignedEntry = (JarEntry) apkJar.getEntry("AndroidManifest.xml");
if (aSignedEntry == null) {
apkJar.close();
throw new CertificateEncodingException("null signed entry!");
}
InputStream tmpIn = apkJar.getInputStream(aSignedEntry);
byte[] buff = new byte[2048];
while (tmpIn.read(buff, 0, buff.length) != -1) {
/*
* NOP - apparently have to READ from the JarEntry before you can
* call getCerficates() and have it return != null. Yay Java.
*/
}
tmpIn.close();
if (aSignedEntry.getCertificates() == null
|| aSignedEntry.getCertificates().length == 0) {
apkJar.close();
throw new CertificateEncodingException("No Certificates found!");
}
Certificate signer = aSignedEntry.getCertificates()[0];
rawCertBytes = signer.getEncoded();
apkJar.close();
/*
* I don't fully understand the loop used here. I've copied it verbatim
* from getsig.java bundled with FDroidServer. I *believe* it is taking
* the raw byte encoding of the certificate & converting it to a byte
* array of the hex representation of the original certificate byte
* array. This is then MD5 sum'd. It's a really bad way to be doing this
* if I'm right... If I'm not right, I really don't know! see lines
* 67->75 in getsig.java bundled with Fdroidserver
*/
byte[] fdroidSig = new byte[rawCertBytes.length * 2];
for (int j = 0; j < rawCertBytes.length; j++) {
byte v = rawCertBytes[j];
int d = (v >> 4) & 0xF;
fdroidSig[j * 2] = (byte) (d >= 10 ? ('a' + d - 10) : ('0' + d));
d = v & 0xF;
fdroidSig[j * 2 + 1] = (byte) (d >= 10 ? ('a' + d - 10) : ('0' + d));
}
apk.sig = Utils.hashBytes(fdroidSig, "md5");
this.apks.add(apk);
}
public boolean isValid() {
if (TextUtils.isEmpty(this.name)
|| TextUtils.isEmpty(this.id))
return false;
if (this.apks == null || this.apks.size() != 1)
return false;
Apk apk = this.apks.get(0);
if (TextUtils.isEmpty(apk.sig))
return false;
File apkFile = apk.installedFile;
if (apkFile == null || !apkFile.canRead())
return false;
return true;
}
public ContentValues toContentValues() { public ContentValues toContentValues() {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();

View File

@ -187,129 +187,26 @@ public class LocalRepoManager {
} }
@TargetApi(9) @TargetApi(9)
public App addApp(Context context, String packageName) { public void addApp(Context context, String packageName) {
// TODO this should become a constructor, i.e. public App(PackageManager pm, String id) App app;
ApplicationInfo appInfo;
PackageInfo packageInfo;
try { try {
appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA); app = new App(context, pm, packageName);
packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES if (!app.isValid())
| PackageManager.GET_PERMISSIONS); return;
PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_META_DATA);
app.icon = getIconFile(packageName, packageInfo.versionCode).getName();
} catch (NameNotFoundException e) { } catch (NameNotFoundException e) {
e.printStackTrace(); e.printStackTrace();
return null; return;
}
App app = new App();
app.name = (String) appInfo.loadLabel(pm);
app.summary = (String) appInfo.loadDescription(pm);
app.icon = getIconFile(packageName, packageInfo.versionCode).getName();
app.id = appInfo.packageName;
if (Build.VERSION.SDK_INT > 8) {
app.added = new Date(packageInfo.firstInstallTime);
app.lastUpdated = new Date(packageInfo.lastUpdateTime);
} else {
app.added = new Date(System.currentTimeMillis());
app.lastUpdated = app.added;
}
app.appInfo = appInfo;
app.apks = new ArrayList<Apk>();
// TODO: use pm.getInstallerPackageName(packageName) for something
File apkFile = new File(appInfo.publicSourceDir);
Apk apk = new Apk();
apk.version = packageInfo.versionName;
apk.vercode = packageInfo.versionCode;
apk.hashType = "sha256";
apk.hash = Utils.getBinaryHash(apkFile, apk.hashType);
apk.added = app.added;
apk.minSdkVersion = Utils.getMinSdkVersion(context, packageName);
apk.id = app.id;
apk.installedFile = apkFile;
if (packageInfo.requestedPermissions == null)
apk.permissions = null;
else
apk.permissions = Utils.CommaSeparatedList.make(
Arrays.asList(packageInfo.requestedPermissions));
apk.apkName = apk.id + "_" + apk.vercode + ".apk";
FeatureInfo[] features = packageInfo.reqFeatures;
if (features != null && features.length > 0) {
List<String> featureNames = new ArrayList<String>(features.length);
for (int i = 0; i < features.length; i++)
featureNames.add(features[i].name);
apk.features = Utils.CommaSeparatedList.make(featureNames);
}
// Signature[] sigs = pkgInfo.signatures;
byte[] rawCertBytes;
try {
JarFile apkJar = new JarFile(apkFile);
JarEntry aSignedEntry = (JarEntry) apkJar.getEntry("AndroidManifest.xml");
if (aSignedEntry == null) {
apkJar.close();
return null;
}
InputStream tmpIn = apkJar.getInputStream(aSignedEntry);
byte[] buff = new byte[2048];
while (tmpIn.read(buff, 0, buff.length) != -1) {
// NOP - apparently have to READ from the JarEntry before you
// can call
// getCerficates() and have it return != null. Yay Java.
}
tmpIn.close();
if (aSignedEntry.getCertificates() == null
|| aSignedEntry.getCertificates().length == 0) {
apkJar.close();
return null;
}
Certificate signer = aSignedEntry.getCertificates()[0];
rawCertBytes = signer.getEncoded();
apkJar.close();
/*
* I don't fully understand the loop used here. I've copied it
* verbatim from getsig.java bundled with FDroidServer. I *believe*
* it is taking the raw byte encoding of the certificate &
* converting it to a byte array of the hex representation of the
* original certificate byte array. This is then MD5 sum'd. It's a
* really bad way to be doing this if I'm right... If I'm not right,
* I really don't know! see lines 67->75 in getsig.java bundled with
* Fdroidserver
*/
byte[] fdroidSig = new byte[rawCertBytes.length * 2];
for (int j = 0; j < rawCertBytes.length; j++) {
byte v = rawCertBytes[j];
int d = (v >> 4) & 0xF;
fdroidSig[j * 2] = (byte) (d >= 10 ? ('a' + d - 10) : ('0' + d));
d = v & 0xF;
fdroidSig[j * 2 + 1] = (byte) (d >= 10 ? ('a' + d - 10) : ('0' + d));
}
apk.sig = Utils.hashBytes(fdroidSig, "md5");
} catch (CertificateEncodingException e) { } catch (CertificateEncodingException e) {
return null; e.printStackTrace();
return;
} catch (IOException e) { } catch (IOException e) {
return null; e.printStackTrace();
return;
} }
Log.i(TAG, "apps.put: " + packageName);
app.apks.add(apk);
if (!validApp(app))
return null;
apps.put(packageName, app); apps.put(packageName, app);
return app;
} }
public void removeApp(String packageName) { public void removeApp(String packageName) {
@ -320,26 +217,6 @@ public class LocalRepoManager {
return new ArrayList<String>(apps.keySet()); return new ArrayList<String>(apps.keySet());
} }
public boolean validApp(App app) {
if (app == null)
return false;
if (app.name == null || app.name.equals(""))
return false;
if (app.id == null | app.id.equals(""))
return false;
if (app.apks == null || app.apks.size() != 1)
return false;
File apkFile = app.apks.get(0).installedFile;
if (apkFile == null || !apkFile.canRead())
return false;
return true;
}
public void copyIconsToRepo() { public void copyIconsToRepo() {
for (App app : apps.values()) { for (App app : apps.values()) {
if (app.apks.size() > 0) { if (app.apks.size() > 0) {