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:
parent
c1a6f545cf
commit
e35a6e05c7
@ -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();
|
||||||
|
@ -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) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user