diff --git a/app/src/main/java/org/fdroid/fdroid/RepoXMLHandler.java b/app/src/main/java/org/fdroid/fdroid/RepoXMLHandler.java index 984b99de7..bb7d53580 100644 --- a/app/src/main/java/org/fdroid/fdroid/RepoXMLHandler.java +++ b/app/src/main/java/org/fdroid/fdroid/RepoXMLHandler.java @@ -26,6 +26,7 @@ import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.data.App; import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.RepoPushRequest; +import org.fdroid.fdroid.data.Schema.ApkTable; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; @@ -141,6 +142,18 @@ public class RepoXMLHandler extends DefaultHandler { curapk.maxSdkVersion = Apk.SDK_VERSION_MAX_VALUE; } break; + case ApkTable.Cols.OBB_MAIN_FILE: + curapk.obbMainFile = str; + break; + case ApkTable.Cols.OBB_MAIN_FILE_SHA256: + curapk.obbMainFileSha256 = str; + break; + case ApkTable.Cols.OBB_PATCH_FILE: + curapk.obbPatchFile = str; + break; + case ApkTable.Cols.OBB_PATCH_FILE_SHA256: + curapk.obbPatchFileSha256 = str; + break; case "added": curapk.added = Utils.parseDate(str, null); break; diff --git a/app/src/main/java/org/fdroid/fdroid/data/Apk.java b/app/src/main/java/org/fdroid/fdroid/data/Apk.java index 5580196ca..592644699 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/Apk.java +++ b/app/src/main/java/org/fdroid/fdroid/data/Apk.java @@ -10,6 +10,7 @@ import android.os.Parcelable; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.Schema.ApkTable.Cols; +import java.io.File; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; @@ -30,6 +31,10 @@ public class Apk extends ValueObject implements Comparable, Parcelable { public int minSdkVersion = SDK_VERSION_MIN_VALUE; // 0 if unknown public int targetSdkVersion = SDK_VERSION_MIN_VALUE; // 0 if unknown public int maxSdkVersion = SDK_VERSION_MAX_VALUE; // "infinity" if not set + public String obbMainFile; + public String obbMainFileSha256; + public String obbPatchFile; + public String obbPatchFileSha256; public Date added; public String[] permissions; // null if empty or // unknown @@ -106,6 +111,18 @@ public class Apk extends ValueObject implements Comparable, Parcelable { case Cols.MAX_SDK_VERSION: maxSdkVersion = cursor.getInt(i); break; + case Cols.OBB_MAIN_FILE: + obbMainFile = cursor.getString(i); + break; + case Cols.OBB_MAIN_FILE_SHA256: + obbMainFileSha256 = cursor.getString(i); + break; + case Cols.OBB_PATCH_FILE: + obbPatchFile = cursor.getString(i); + break; + case Cols.OBB_PATCH_FILE_SHA256: + obbPatchFileSha256 = cursor.getString(i); + break; case Cols.NAME: apkName = cursor.getString(i); break; @@ -146,13 +163,73 @@ public class Apk extends ValueObject implements Comparable, Parcelable { } } - public String getUrl() { + private void checkRepoAddress() { if (repoAddress == null || apkName == null) { throw new IllegalStateException("Apk needs to have both Schema.ApkTable.Cols.REPO_ADDRESS and Schema.ApkTable.Cols.NAME set in order to calculate URL."); } + } + + public String getUrl() { + checkRepoAddress(); return repoAddress + "/" + apkName.replace(" ", "%20"); } + /** + * Get the URL to download the main expansion file, the primary + * expansion file for additional resources required by your application. + * The filename will always have the format: + * "main.versionCode.packageName.obb" + * + * @return a URL to download the OBB file that matches this APK + * @see #getPatchObbUrl() + * @see APK Expansion Files + */ + public String getMainObbUrl() { + if (repoAddress == null || obbMainFile == null) { + return null; + } + checkRepoAddress(); + return repoAddress + "/" + obbMainFile; + } + + /** + * Get the URL to download the optional patch expansion file, which + * is intended for small updates to the main expansion file. + * The filename will always have the format: + * "patch.versionCode.packageName.obb" + * + * @return a URL to download the OBB file that matches this APK + * @see #getMainObbUrl() + * @see APK Expansion Files + */ + public String getPatchObbUrl() { + if (repoAddress == null || obbPatchFile == null) { + return null; + } + checkRepoAddress(); + return repoAddress + "/" + obbPatchFile; + } + + /** + * Get the local {@link File} to the "main" OBB file. + */ + public File getMainObbFile() { + if (obbMainFile == null) { + return null; + } + return new File(App.getObbDir(packageName), obbMainFile); + } + + /** + * Get the local {@link File} to the "patch" OBB file. + */ + public File getPatchObbFile() { + if (obbPatchFile == null) { + return null; + } + return new File(App.getObbDir(packageName), obbPatchFile); + } + public ArrayList getFullPermissionList() { if (this.permissions == null) { return new ArrayList<>(); @@ -180,7 +257,8 @@ public class Apk extends ValueObject implements Comparable, Parcelable { * FDroid just includes the constant name in the apk list, so we prefix it * with "android.permission." * - * see https://gitlab.com/fdroid/fdroidserver/blob/master/fdroidserver/update.py#L535# + * @see + * More info into index - size, permissions, features, sdk version */ private static String fdroidToAndroidPermission(String permission) { if (!permission.contains(".")) { @@ -210,6 +288,10 @@ public class Apk extends ValueObject implements Comparable, Parcelable { values.put(Cols.MIN_SDK_VERSION, minSdkVersion); values.put(Cols.TARGET_SDK_VERSION, targetSdkVersion); values.put(Cols.MAX_SDK_VERSION, maxSdkVersion); + values.put(Cols.OBB_MAIN_FILE, obbMainFile); + values.put(Cols.OBB_MAIN_FILE_SHA256, obbMainFileSha256); + values.put(Cols.OBB_PATCH_FILE, obbPatchFile); + values.put(Cols.OBB_PATCH_FILE_SHA256, obbPatchFileSha256); values.put(Cols.ADDED_DATE, Utils.formatDate(added, "")); values.put(Cols.PERMISSIONS, Utils.serializeCommaSeparatedString(permissions)); values.put(Cols.FEATURES, Utils.serializeCommaSeparatedString(features)); @@ -245,6 +327,10 @@ public class Apk extends ValueObject implements Comparable, Parcelable { dest.writeInt(this.minSdkVersion); dest.writeInt(this.targetSdkVersion); dest.writeInt(this.maxSdkVersion); + dest.writeString(this.obbMainFile); + dest.writeString(this.obbMainFileSha256); + dest.writeString(this.obbPatchFile); + dest.writeString(this.obbPatchFileSha256); dest.writeLong(this.added != null ? this.added.getTime() : -1); dest.writeStringArray(this.permissions); dest.writeStringArray(this.features); @@ -271,6 +357,10 @@ public class Apk extends ValueObject implements Comparable, Parcelable { this.minSdkVersion = in.readInt(); this.targetSdkVersion = in.readInt(); this.maxSdkVersion = in.readInt(); + this.obbMainFile = in.readString(); + this.obbMainFileSha256 = in.readString(); + this.obbPatchFile = in.readString(); + this.obbPatchFileSha256 = in.readString(); long tmpAdded = in.readLong(); this.added = tmpAdded == -1 ? null : new Date(tmpAdded); this.permissions = in.createStringArray(); diff --git a/app/src/main/java/org/fdroid/fdroid/data/App.java b/app/src/main/java/org/fdroid/fdroid/data/App.java index 611072bf9..e7d96ba1d 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/App.java +++ b/app/src/main/java/org/fdroid/fdroid/data/App.java @@ -9,22 +9,27 @@ import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.content.res.XmlResourceParser; import android.database.Cursor; +import android.os.Environment; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.util.Log; +import org.apache.commons.io.filefilter.RegexFileFilter; import org.fdroid.fdroid.AppFilter; import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.Utils; +import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.File; +import java.io.FileFilter; import java.io.IOException; import java.io.InputStream; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; +import java.util.Arrays; import java.util.Date; import java.util.Enumeration; import java.util.HashSet; @@ -33,8 +38,6 @@ import java.util.jar.JarFile; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols; - public class App extends ValueObject implements Comparable, Parcelable { private static final String TAG = "App"; @@ -272,6 +275,17 @@ public class App extends ValueObject implements Comparable, Parcelable { initApkFromApkFile(context, this.installedApk, packageInfo, apkFile); } + /** + * Get the directory where APK Expansion Files aka OBB files are stored for the app as + * specified by {@code packageName}. + * + * @see APK Expansion Files + */ + public static File getObbDir(String packageName) { + return new File(Environment.getExternalStorageDirectory().getAbsolutePath() + + "/Android/obb/" + packageName); + } + private void setFromPackageInfo(PackageManager pm, PackageInfo packageInfo) { this.packageName = packageInfo.packageName; @@ -324,6 +338,29 @@ public class App extends ValueObject implements Comparable, Parcelable { initInstalledApk(context, apk, packageInfo, apkFile); } + public static void initInstalledObbFiles(Apk apk) { + File obbdir = getObbDir(apk.packageName); + FileFilter filter = new RegexFileFilter("(main|patch)\\.[0-9-][0-9]*\\." + apk.packageName + "\\.obb"); + File[] files = obbdir.listFiles(filter); + if (files == null) { + return; + } + Arrays.sort(files); + for (File f : files) { + String filename = f.getName(); + String[] segments = filename.split("\\."); + if (Integer.parseInt(segments[1]) <= apk.versionCode) { + if ("main".equals(segments[0])) { + apk.obbMainFile = filename; + apk.obbMainFileSha256 = Utils.getBinaryHash(f, apk.hashType); + } else if ("patch".equals(segments[0])) { + apk.obbPatchFile = filename; + apk.obbPatchFileSha256 = Utils.getBinaryHash(f, apk.hashType); + } + } + } + } + private void initInstalledApk(Context context, Apk apk, PackageInfo packageInfo, SanitizedFile apkFile) throws IOException, CertificateEncodingException { apk.compatible = true; @@ -339,6 +376,8 @@ public class App extends ValueObject implements Comparable, Parcelable { apk.apkName = apk.packageName + "_" + apk.versionCode + ".apk"; apk.installedFile = apkFile; + initInstalledObbFiles(apk); + JarFile apkJar = new JarFile(apkFile); HashSet abis = new HashSet<>(3); Pattern pattern = Pattern.compile("^lib/([a-z0-9-]+)/.*"); diff --git a/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java b/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java index 9c238295d..51270d448 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java +++ b/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java @@ -92,6 +92,10 @@ class DBHelper extends SQLiteOpenHelper { + ApkTable.Cols.MIN_SDK_VERSION + " integer, " + ApkTable.Cols.TARGET_SDK_VERSION + " integer, " + ApkTable.Cols.MAX_SDK_VERSION + " integer, " + + ApkTable.Cols.OBB_MAIN_FILE + " string, " + + ApkTable.Cols.OBB_MAIN_FILE_SHA256 + " string, " + + ApkTable.Cols.OBB_PATCH_FILE + " string, " + + ApkTable.Cols.OBB_PATCH_FILE_SHA256 + " string, " + ApkTable.Cols.PERMISSIONS + " string, " + ApkTable.Cols.FEATURES + " string, " + ApkTable.Cols.NATIVE_CODE + " string, " @@ -154,7 +158,7 @@ class DBHelper extends SQLiteOpenHelper { + " );"; private static final String DROP_TABLE_INSTALLED_APP = "DROP TABLE " + InstalledAppTable.NAME + ";"; - private static final int DB_VERSION = 63; + private static final int DB_VERSION = 64; private final Context context; @@ -354,6 +358,24 @@ class DBHelper extends SQLiteOpenHelper { lowerCaseApkHashes(db, oldVersion); supportRepoPushRequests(db, oldVersion); migrateToPackageTable(db, oldVersion); + addObbFiles(db, oldVersion); + } + + private void addObbFiles(SQLiteDatabase db, int oldVersion) { + if (oldVersion >= 64) { + return; + } + Utils.debugLog(TAG, "Adding " + ApkTable.Cols.OBB_MAIN_FILE + + ", " + ApkTable.Cols.OBB_PATCH_FILE + + ", and hash columns to " + ApkTable.NAME); + db.execSQL("alter table " + ApkTable.NAME + " add column " + + ApkTable.Cols.OBB_MAIN_FILE + " string"); + db.execSQL("alter table " + ApkTable.NAME + " add column " + + ApkTable.Cols.OBB_MAIN_FILE_SHA256 + " string"); + db.execSQL("alter table " + ApkTable.NAME + " add column " + + ApkTable.Cols.OBB_PATCH_FILE + " string"); + db.execSQL("alter table " + ApkTable.NAME + " add column " + + ApkTable.Cols.OBB_PATCH_FILE_SHA256 + " string"); } private void migrateToPackageTable(SQLiteDatabase db, int oldVersion) { @@ -477,6 +499,10 @@ class DBHelper extends SQLiteOpenHelper { + ApkTable.Cols.MIN_SDK_VERSION + " integer, " + ApkTable.Cols.TARGET_SDK_VERSION + " integer, " + ApkTable.Cols.MAX_SDK_VERSION + " integer, " + + ApkTable.Cols.OBB_MAIN_FILE + " string, " + + ApkTable.Cols.OBB_MAIN_FILE_SHA256 + " string, " + + ApkTable.Cols.OBB_PATCH_FILE + " string, " + + ApkTable.Cols.OBB_PATCH_FILE_SHA256 + " string, " + ApkTable.Cols.PERMISSIONS + " string, " + ApkTable.Cols.FEATURES + " string, " + ApkTable.Cols.NATIVE_CODE + " string, " @@ -502,6 +528,10 @@ class DBHelper extends SQLiteOpenHelper { ApkTable.Cols.MIN_SDK_VERSION, ApkTable.Cols.TARGET_SDK_VERSION, ApkTable.Cols.MAX_SDK_VERSION, + ApkTable.Cols.OBB_MAIN_FILE, + ApkTable.Cols.OBB_MAIN_FILE_SHA256, + ApkTable.Cols.OBB_PATCH_FILE, + ApkTable.Cols.OBB_PATCH_FILE_SHA256, ApkTable.Cols.PERMISSIONS, ApkTable.Cols.FEATURES, ApkTable.Cols.NATIVE_CODE, diff --git a/app/src/main/java/org/fdroid/fdroid/data/Schema.java b/app/src/main/java/org/fdroid/fdroid/data/Schema.java index f14cb1484..b910c107f 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/Schema.java +++ b/app/src/main/java/org/fdroid/fdroid/data/Schema.java @@ -165,6 +165,10 @@ public interface Schema { String MIN_SDK_VERSION = "minSdkVersion"; String TARGET_SDK_VERSION = "targetSdkVersion"; String MAX_SDK_VERSION = "maxSdkVersion"; + String OBB_MAIN_FILE = "obbMainFile"; + String OBB_MAIN_FILE_SHA256 = "obbMainFileSha256"; + String OBB_PATCH_FILE = "obbPatchFile"; + String OBB_PATCH_FILE_SHA256 = "obbPatchFileSha256"; String PERMISSIONS = "permissions"; String FEATURES = "features"; String NATIVE_CODE = "nativecode"; @@ -188,6 +192,7 @@ public interface Schema { String[] ALL_COLS = { APP_ID, VERSION_NAME, REPO_ID, HASH, VERSION_CODE, NAME, SIZE, SIGNATURE, SOURCE_NAME, MIN_SDK_VERSION, TARGET_SDK_VERSION, MAX_SDK_VERSION, + OBB_MAIN_FILE, OBB_MAIN_FILE_SHA256, OBB_PATCH_FILE, OBB_PATCH_FILE_SHA256, PERMISSIONS, FEATURES, NATIVE_CODE, HASH_TYPE, ADDED_DATE, IS_COMPATIBLE, INCOMPATIBLE_REASONS, }; @@ -198,6 +203,7 @@ public interface Schema { String[] ALL = { _ID, APP_ID, Package.PACKAGE_NAME, VERSION_NAME, REPO_ID, HASH, VERSION_CODE, NAME, SIZE, SIGNATURE, SOURCE_NAME, MIN_SDK_VERSION, TARGET_SDK_VERSION, MAX_SDK_VERSION, + OBB_MAIN_FILE, OBB_MAIN_FILE_SHA256, OBB_PATCH_FILE, OBB_PATCH_FILE_SHA256, PERMISSIONS, FEATURES, NATIVE_CODE, HASH_TYPE, ADDED_DATE, IS_COMPATIBLE, Repo.VERSION, Repo.ADDRESS, INCOMPATIBLE_REASONS, }; diff --git a/app/src/test/java/org/fdroid/fdroid/updater/RepoXMLHandlerTest.java b/app/src/test/java/org/fdroid/fdroid/updater/RepoXMLHandlerTest.java index 12751a40a..193c8dc23 100644 --- a/app/src/test/java/org/fdroid/fdroid/updater/RepoXMLHandlerTest.java +++ b/app/src/test/java/org/fdroid/fdroid/updater/RepoXMLHandlerTest.java @@ -26,6 +26,7 @@ import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Log; +import org.apache.commons.io.FileUtils; import org.fdroid.fdroid.BuildConfig; import org.fdroid.fdroid.RepoXMLHandler; import org.fdroid.fdroid.data.Apk; @@ -33,15 +34,18 @@ import org.fdroid.fdroid.data.App; import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.RepoPushRequest; import org.fdroid.fdroid.mock.MockRepo; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricGradleTestRunner; import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowLog; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import java.io.BufferedInputStream; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; @@ -70,6 +74,32 @@ public class RepoXMLHandlerTest { private static final String FAKE_SIGNING_CERT = "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345"; + @Before + public void setUp() { + ShadowLog.stream = System.out; + } + + @Test + public void testObbIndex() throws IOException { + writeResourceToObbDir("main.1101613.obb.main.twoversions.obb"); + writeResourceToObbDir("main.1101615.obb.main.twoversions.obb"); + writeResourceToObbDir("main.1434483388.obb.main.oldversion.obb"); + writeResourceToObbDir("main.1619.obb.mainpatch.current.obb"); + writeResourceToObbDir("patch.1619.obb.mainpatch.current.obb"); + RepoDetails actualDetails = getFromFile("obbIndex.xml"); + for (Apk indexApk : actualDetails.apks) { + Apk localApk = new Apk(); + localApk.packageName = indexApk.packageName; + localApk.versionCode = indexApk.versionCode; + localApk.hashType = indexApk.hashType; + App.initInstalledObbFiles(localApk); + assertEquals(indexApk.obbMainFile, localApk.obbMainFile); + assertEquals(indexApk.obbMainFileSha256, localApk.obbMainFileSha256); + assertEquals(indexApk.obbPatchFile, localApk.obbPatchFile); + assertEquals(indexApk.obbPatchFileSha256, localApk.obbPatchFileSha256); + } + } + @Test public void testSimpleIndex() { Repo expectedRepo = new Repo(); @@ -871,4 +901,12 @@ public class RepoXMLHandlerTest { } } + private void writeResourceToObbDir(String assetName) throws IOException { + InputStream input = getClass().getClassLoader().getResourceAsStream(assetName); + String packageName = assetName.substring(assetName.indexOf("obb"), + assetName.lastIndexOf('.')); + File f = new File(App.getObbDir(packageName), assetName); + FileUtils.copyToFile(input, f); + input.close(); + } } diff --git a/app/src/test/resources/main.1101613.obb.main.twoversions.obb b/app/src/test/resources/main.1101613.obb.main.twoversions.obb new file mode 100644 index 000000000..421376db9 --- /dev/null +++ b/app/src/test/resources/main.1101613.obb.main.twoversions.obb @@ -0,0 +1 @@ +dummy diff --git a/app/src/test/resources/main.1101615.obb.main.twoversions.obb b/app/src/test/resources/main.1101615.obb.main.twoversions.obb new file mode 100644 index 000000000..421376db9 --- /dev/null +++ b/app/src/test/resources/main.1101615.obb.main.twoversions.obb @@ -0,0 +1 @@ +dummy diff --git a/app/src/test/resources/main.1434483388.obb.main.oldversion.obb b/app/src/test/resources/main.1434483388.obb.main.oldversion.obb new file mode 100644 index 000000000..421376db9 --- /dev/null +++ b/app/src/test/resources/main.1434483388.obb.main.oldversion.obb @@ -0,0 +1 @@ +dummy diff --git a/app/src/test/resources/main.1619.obb.mainpatch.current.obb b/app/src/test/resources/main.1619.obb.mainpatch.current.obb new file mode 100644 index 000000000..421376db9 --- /dev/null +++ b/app/src/test/resources/main.1619.obb.mainpatch.current.obb @@ -0,0 +1 @@ +dummy diff --git a/app/src/test/resources/obbIndex.xml b/app/src/test/resources/obbIndex.xml new file mode 100644 index 000000000..197d0eddf --- /dev/null +++ b/app/src/test/resources/obbIndex.xml @@ -0,0 +1 @@ +This is a repository of apps to be used with F-Droid. Applications in this repository are either official binaries built by the original application developers, or are binaries built from source by the admin of f-droid.org using the tools on https://gitlab.com/u/fdroid. obb.main.oldversion2016-06-272016-06-27OBB Main Old Versionobb.main.oldversion.1444412523.png<p>No description available</p>GPLv3DevelopmentDevelopmenthttps://github.com/eighthave/urzip1Fi5xUHiAPRKxHvyUGVFGt9extBe8Srdbk999999990.11444412523obb.main.oldversion_1444412523.apk7562a36c9e2b38013b96663cf41f0f290dc7a248a81befa8d89e14f390c94c7fb4964fd759edaa54e65bb476d027688011470418main.1434483388.obb.main.oldversion.obbd3eb539a556352f3f47881d71fb0e5777b2f3e9a4251d283c18c67ce996774b72016-06-27obb.main.twoversions2016-06-272016-06-27OBB Main Two Versionsobb.main.twoversions.1101617.png<p>No description available</p>GPLv3DevelopmentDevelopmenthttps://github.com/eighthave/urzip1Fi5xUHiAPRKxHvyUGVFGt9extBe8Srdbk999999990.11101617obb.main.twoversions_1101617.apk9bc74566f089ef030ac33e7fbd99d92f1a38f363fb499fed138d9e7b774e821cb4964fd759edaa54e65bb476d027688011481418main.1101615.obb.main.twoversions.obbd3eb539a556352f3f47881d71fb0e5777b2f3e9a4251d283c18c67ce996774b72016-06-270.11101615obb.main.twoversions_1101615.apk7b0b7b9ba248e15751a16e3a0e01e1e24cbb673686c38422030cb75d5c33f0bbb4964fd759edaa54e65bb476d027688011480418main.1101615.obb.main.twoversions.obbd3eb539a556352f3f47881d71fb0e5777b2f3e9a4251d283c18c67ce996774b72016-06-270.11101613obb.main.twoversions_1101613.apkcce97a52ff18d843185be7f22ecb1a557c36b7a9f8ba07a8be94e328e00b35dcb4964fd759edaa54e65bb476d027688011477418main.1101613.obb.main.twoversions.obbd3eb539a556352f3f47881d71fb0e5777b2f3e9a4251d283c18c67ce996774b72016-06-27obb.mainpatch.current2016-06-272016-06-27OBB Main/Patch Currentobb.mainpatch.current.1619.png<p>No description available</p>GPLv3DevelopmentDevelopmenthttps://github.com/eighthave/urzip1Fi5xUHiAPRKxHvyUGVFGt9extBe8Srdbk999999990.11619obb.mainpatch.current_1619.apkeda5fc3ecfdac3252717e36bdbc9820865baeef162264af9ba5db7364f0e7a0cb4964fd759edaa54e65bb476d027688011479418main.1619.obb.mainpatch.current.obbd3eb539a556352f3f47881d71fb0e5777b2f3e9a4251d283c18c67ce996774b7patch.1619.obb.mainpatch.current.obbd3eb539a556352f3f47881d71fb0e5777b2f3e9a4251d283c18c67ce996774b72016-06-27info.guardianproject.urzip2016-06-272016-06-27urzip-πÇÇπÇÇ现代汉语通用字-български-عربي1234一个实用工具,获取已安装在您的设备上的应用的有关信息info.guardianproject.urzip.100.png<p>It’s Urzip 是一个获得已安装 APK 相关信息的实用工具。它从您的设备上已安装的所有应用开始,一键触摸即可显示 APK 的指纹,并且提供到达 virustotal.com 和 androidobservatory.org 的快捷链接,让您方便地了解特定 APK 的档案。它还可以让您导出签名证书和生成 ApkSignaturePin Pin 文件供 TrustedIntents 库使用。</p><p>★ Urzip 支持下列语言: Deutsch, English, español, suomi, 日本語, 한국어, Norsk, português (Portugal), Русский, Slovenščina, Türkçe 没看到您的语言?帮忙翻译本应用吧: https://www.transifex.com/projects/p/urzip</p><p>★ 致用户:我们还缺少你喜欢的功能?发现了一个 bug?请告诉我们!我们乐于听取您的意见。请发送电子邮件至: support@guardianproject.info 或者加入我们的聊天室 https://guardianproject.info/contact</p>GPLv3Development,GuardianProjectDevelopmenthttps://dev.guardianproject.info/projects/urziphttps://github.com/guardianproject/urziphttps://dev.guardianproject.info/projects/urzip/issues1Fi5xUHiAPRKxHvyUGVFGt9extBe8Srdbk99999990.1100urzip-πÇÇπÇÇ现代汉语通用字-български-عربي1234.apk15c0ec72c74a3791f42cdb43c57df0fb11a4dbb656851bbb8cf05b26a8372789b4964fd759edaa54e65bb476d0276880114714182016-06-27 \ No newline at end of file diff --git a/app/src/test/resources/patch.1619.obb.mainpatch.current.obb b/app/src/test/resources/patch.1619.obb.mainpatch.current.obb new file mode 100644 index 000000000..421376db9 --- /dev/null +++ b/app/src/test/resources/patch.1619.obb.mainpatch.current.obb @@ -0,0 +1 @@ +dummy