From b7b8865325a09f788503d83f8edf4b5ab27d9a0f Mon Sep 17 00:00:00 2001 From: Peter Serwylo <peter@serwylo.com> Date: Tue, 20 Jun 2017 09:07:49 +1000 Subject: [PATCH 1/7] Extract useful test functions into TestUtils These will be used by a "preferred sig" test soon. --- .../java/org/fdroid/fdroid/TestUtils.java | 53 ++++++ .../fdroid/data/SuggestedVersionTest.java | 162 ++++++------------ 2 files changed, 110 insertions(+), 105 deletions(-) diff --git a/app/src/test/java/org/fdroid/fdroid/TestUtils.java b/app/src/test/java/org/fdroid/fdroid/TestUtils.java index caf258ea1..11fd71c9c 100644 --- a/app/src/test/java/org/fdroid/fdroid/TestUtils.java +++ b/app/src/test/java/org/fdroid/fdroid/TestUtils.java @@ -2,9 +2,16 @@ package org.fdroid.fdroid; import android.content.ContentProvider; import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; import android.content.ContextWrapper; import android.content.pm.ProviderInfo; +import org.fdroid.fdroid.data.App; +import org.fdroid.fdroid.data.Repo; +import org.fdroid.fdroid.data.RepoProvider; +import org.fdroid.fdroid.data.RepoProviderTest; +import org.fdroid.fdroid.data.Schema; import org.mockito.AdditionalAnswers; import org.robolectric.Robolectric; import org.robolectric.RuntimeEnvironment; @@ -15,6 +22,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.security.NoSuchAlgorithmException; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -25,6 +33,51 @@ public class TestUtils { @SuppressWarnings("unused") private static final String TAG = "TestUtils"; // NOPMD + public static final String FDROID_CERT = "308202ed308201d5a003020102020426ffa009300d06092a864886f70d01010b05003027310b300906035504061302444531183016060355040a130f4e4f47415050532050726f6a656374301e170d3132313030363132303533325a170d3337303933303132303533325a3027310b300906035504061302444531183016060355040a130f4e4f47415050532050726f6a65637430820122300d06092a864886f70d01010105000382010f003082010a02820101009a8d2a5336b0eaaad89ce447828c7753b157459b79e3215dc962ca48f58c2cd7650df67d2dd7bda0880c682791f32b35c504e43e77b43c3e4e541f86e35a8293a54fb46e6b16af54d3a4eda458f1a7c8bc1b7479861ca7043337180e40079d9cdccb7e051ada9b6c88c9ec635541e2ebf0842521c3024c826f6fd6db6fd117c74e859d5af4db04448965ab5469b71ce719939a06ef30580f50febf96c474a7d265bb63f86a822ff7b643de6b76e966a18553c2858416cf3309dd24278374bdd82b4404ef6f7f122cec93859351fc6e5ea947e3ceb9d67374fe970e593e5cd05c905e1d24f5a5484f4aadef766e498adf64f7cf04bddd602ae8137b6eea40722d0203010001a321301f301d0603551d0e04160414110b7aa9ebc840b20399f69a431f4dba6ac42a64300d06092a864886f70d01010b0500038201010007c32ad893349cf86952fb5a49cfdc9b13f5e3c800aece77b2e7e0e9c83e34052f140f357ec7e6f4b432dc1ed542218a14835acd2df2deea7efd3fd5e8f1c34e1fb39ec6a427c6e6f4178b609b369040ac1f8844b789f3694dc640de06e44b247afed11637173f36f5886170fafd74954049858c6096308fc93c1bc4dd5685fa7a1f982a422f2a3b36baa8c9500474cf2af91c39cbec1bc898d10194d368aa5e91f1137ec115087c31962d8f76cd120d28c249cf76f4c70f5baa08c70a7234ce4123be080cee789477401965cfe537b924ef36747e8caca62dfefdd1a6288dcb1c4fd2aaa6131a7ad254e9742022cfd597d2ca5c660ce9e41ff537e5a4041e37"; // NOCHECKSTYLE LineLength + public static final String UPSTREAM_CERT = "308204e1308202c9a0030201020204483450fa300d06092a864886f70d01010b050030213110300e060355040b1307462d44726f6964310d300b06035504031304736f7661301e170d3136303832333133333131365a170d3434303130393133333131365a30213110300e060355040b1307462d44726f6964310d300b06035504031304736f766130820222300d06092a864886f70d01010105000382020f003082020a0282020100dfdcd120f3ab224999dddf4ea33ea588d295e4d7130bef48c143e9d76e5c0e0e9e5d45e64208e35feebc79a83f08939dd6a343b7d1e2179930a105a1249ccd36d88ff3feffc6e4dc53dae0163a7876dd45ecc1ddb0adf5099aa56c1a84b52affcd45d0711ffa4de864f35ac0333ebe61ea8673eeda35a88f6af678cc4d0f80b089338ac8f2a8279a64195c611d19445cab3fd1a020afed9bd739bb95142fb2c00a8f847db5ef3325c814f8eb741bacf86ed3907bfe6e4564d2de5895df0c263824e0b75407589bae2d3a4666c13b92102d8781a8ee9bb4a5a1a78c4a9c21efdaf5584da42e84418b28f5a81d0456a3dc5b420991801e6b21e38c99bbe018a5b2d690894a114bc860d35601416aa4dc52216aff8a288d4775cddf8b72d45fd2f87303a8e9c0d67e442530be28eaf139894337266e0b33d57f949256ab32083bcc545bc18a83c9ab8247c12aea037e2b68dee31c734cb1f04f241d3b94caa3a2b258ffaf8e6eae9fbbe029a934dc0a0859c5f120334812693a1c09352340a39f2a678dbc1afa2a978bfee43afefcb7e224a58af2f3d647e5745db59061236b8af6fcfd93b3602f9e456978534f3a7851e800071bf56da80401c81d91c45f82568373af0576b1cc5eef9b85654124b6319770be3cdba3fbebe3715e8918fb6c8966624f3d0e815effac3d2ee06dd34ab9c693218b2c7c06ba99d6b74d4f17b8c3cb0203010001a321301f301d0603551d0e04160414d62bee9f3798509546acc62eb1de14b08b954d4f300d06092a864886f70d01010b05000382020100743f7c5692085895f9d1fffad390fb4202c15f123ed094df259185960fd6dadf66cb19851070f180297bba4e6996a4434616573b375cfee94fee73a4505a7ec29136b7e6c22e6436290e3686fe4379d4e3140ec6a08e70cfd3ed5b634a5eb5136efaaabf5f38e0432d3d79568a556970b8cfba2972f5d23a3856d8a981b9e9bbbbb88f35e708bde9cbc5f681cbd974085b9da28911296fe2579fa64bbe9fa0b93475a7a8db051080b0c5fade0d1c018e7858cd4cbe95145b0620e2f632cbe0f8af9cbf22e2fdaa72245ae31b0877b07181cc69dd2df74454251d8de58d25e76354abe7eb690f22e59b08795a8f2c98c578e0599503d9085927634072c82c9f82abd50fd12b8fd1a9d1954eb5cc0b4cfb5796b5aaec0356643b4a65a368442d92ef94edd3ac6a2b7fe3571b8cf9f462729228aab023ef9183f73792f5379633ccac51079177d604c6bc1873ada6f07d8da6d68c897e88a5fa5d63fdb8df820f46090e0716e7562dd3c140ba279a65b996f60addb0abe29d4bf2f5abe89480771d492307b926d91f02f341b2148502903c43d40f3c6c86a811d060711f0698b384acdcc0add44eb54e42962d3d041accc715afd49407715adc09350cb55e8d9281a3b0b6b5fcd91726eede9b7c8b13afdebb2c2b377629595f1096ba62fb14946dbac5f3c5f0b4e5b712e7acc7dcf6c46cdc5e6d6dfdeee55a0c92c2d70f080ac6"; // NOCHECKSTYLE LineLength + public static final String THIRD_PARTY_CERT = "308204e1308202c9a0030201020204483450fa300d06092a864886f70d01010b050030213110300e060355040b130abcdeabcde012340123400b06035504031304736f7661301e170d3136303832333133333131365a170d3434303130393133333131365a30213110300e060355040b1307462d44726f6964310d300b06035504031304736f766130820222300d06092a864886f70d01010105000382020f003082020a0282020100dfdcd120f3ab224999dddf4ea33ea588d295e4d7130bef48c143e9d76e5c0e0e9e5d45e64208e35feebc79a83f08939dd6a343b7d1e2179930a105a1249ccd36d88ff3feffc6e4dc53dae0163a7876dd45ecc1ddb0adf5099aa56c1a84b52affcd45d0711ffa4de864f35ac0333ebe61ea8673eeda35a88f6af678cc4d0f80b089338ac8f2a8279a64195c611d19445cab3fd1a020afed9bd739bb95142fb2c00a8f847db5ef3325c814f8eb741bacf86ed3907bfe6e4564d2de5895df0c263824e0b75407589bae2d3a4666c13b92102d8781a8ee9bb4a5a1a78c4a9c21efdaf5584da42e84418b28f5a81d0456a3dc5b420991801e6b21e38c99bbe018a5b2d690894a114bc860d35601416aa4dc52216aff8a288d4775cddf8b72d45fd2f87303a8e9c0d67e442530be28eaf139894337266e0b33d57f949256ab32083bcc545bc18a83c9ab8247c12aea037e2b68dee31c734cb1f04f241d3b94caa3a2b258ffaf8e6eae9fbbe029a934dc0a0859c5f120334812693a1c09352340a39f2a678dbc1afa2a978bfee43afefcb7e224a58af2f3d647e5745db59061236b8af6fcfd93b3602f9e456978534f3a7851e800071bf56da80401c81d91c45f82568373af0576b1cc5eef9b85654124b6319770be3cdba3fbebe3715e8918fb6c8966624f3d0e815effac3d2ee06dd34ab9c693218b2c7c06ba99d6b74d4f17b8c3cb0203010001a321301f301d0603551d0e04160414d62bee9f3798509546acc62eb1de14b08b954d4f300d06092a864886f70d01010b05000382020100743f7c5692085895f9d1fffad390fb4202c15f123ed094df259185960fd6dadf66cb19851070f180297bba4e6996a4434616573b375cfee94fee73a4505a7ec29136b7e6c22e6436290e3686fe4379d4e3140ec6a08e70cfd3ed5b634a5eb5136efaaabf5f38e0432d3d79568a556970b8cfba2972f5d23a3856d8a981b9e9bbbbb88f35e708bde9cbc5f681cbd974085b9da28911296fe2579fa64bbe9fa0b93475a7a8db051080b0c5fade0d1c018e7858cd4cbe95145b0620e2f632cbe0f8af9cbf22e2fdaa72245ae31b0877b07181cc69dd2df74454251d8de58d25e76354abe7eb690f22e59b08795a8f2c98c578e0599503d9085927634072c82c9f82abd50fd12b8fd1a9d1954eb5cc0b4cfb5796b5aaec0356643b4a65a368442d92ef94edd3ac6a2b7fe3571b8cf9f462729228aab023ef9183f73792f5379633ccac51079177d604c6bc1873ada6f07d8da6d68c897e88a5fa5d63fdb8df820f46090e0716e7562dd3c140ba279a65b996f60addb0abe29d4bf2f5abe89480771d492307b926d91f02f341b2148502903c43d40f3c6c86a811d060711f0698b384acdcc0add44eb54e42962d3d041accc715afd49407715adc09350cb55e8d9281a3b0b6b5fcd91726eede9b7c8b13afdebb2c2b377629595f1096ba62fb14946dbac5f3c5f0b4e5b712e7acc7dcf6c46cdc5e6d6dfdeee55a0c92c2d70f080ac6"; // NOCHECKSTYLE LineLength + + public static final String FDROID_SIG; + public static final String UPSTREAM_SIG; + public static final String THIRD_PARTY_SIG; + + static { + // Some code requires the full certificate (e.g. when we mock PackageInfo to give to the + // installed app provider), while others requires the hashed certificate (e.g. inserting + // into the apk provider directly, without the need to mock anything). + try { + FDROID_SIG = new Hasher("MD5", FDROID_CERT.getBytes()).getHash(); + UPSTREAM_SIG = new Hasher("MD5", UPSTREAM_CERT.getBytes()).getHash(); + THIRD_PARTY_SIG = new Hasher("MD5", THIRD_PARTY_CERT.getBytes()).getHash(); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } + } + + public static void insertApk(Context context, App app, int versionCode, String signature) { + ContentValues values = new ContentValues(); + values.put(Schema.ApkTable.Cols.SIGNATURE, signature); + Assert.insertApk(context, app, versionCode, values); + } + + public static App insertApp(Context context, String packageName, String appName, int upstreamVersionCode, + String repoUrl) { + Repo repo = ensureRepo(context, repoUrl); + ContentValues values = new ContentValues(); + values.put(Schema.AppMetadataTable.Cols.REPO_ID, repo.getId()); + values.put(Schema.AppMetadataTable.Cols.UPSTREAM_VERSION_CODE, upstreamVersionCode); + return Assert.insertApp(context, packageName, appName, values); + } + + public static Repo ensureRepo(Context context, String repoUrl) { + Repo existing = RepoProvider.Helper.findByAddress(context, repoUrl); + if (existing != null) { + return existing; + } + + return RepoProviderTest.insertRepo(context, repoUrl, "", "", ""); + } + public static <T extends ContentProvider> void registerContentProvider(String authority, Class<T> providerClass) { ProviderInfo info = new ProviderInfo(); info.authority = authority; diff --git a/app/src/test/java/org/fdroid/fdroid/data/SuggestedVersionTest.java b/app/src/test/java/org/fdroid/fdroid/data/SuggestedVersionTest.java index f283f27d8..010e2b638 100644 --- a/app/src/test/java/org/fdroid/fdroid/data/SuggestedVersionTest.java +++ b/app/src/test/java/org/fdroid/fdroid/data/SuggestedVersionTest.java @@ -1,12 +1,8 @@ package org.fdroid.fdroid.data; import android.app.Application; -import android.content.ContentValues; -import android.content.Context; -import org.fdroid.fdroid.Assert; import org.fdroid.fdroid.BuildConfig; -import org.fdroid.fdroid.Hasher; import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.TestUtils; import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols; @@ -17,7 +13,6 @@ import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import java.security.NoSuchAlgorithmException; import java.util.Collections; import java.util.List; @@ -27,27 +22,6 @@ import static org.junit.Assert.assertEquals; @RunWith(RobolectricTestRunner.class) public class SuggestedVersionTest extends FDroidProviderTest { - private static final String FDROID_CERT = "308202ed308201d5a003020102020426ffa009300d06092a864886f70d01010b05003027310b300906035504061302444531183016060355040a130f4e4f47415050532050726f6a656374301e170d3132313030363132303533325a170d3337303933303132303533325a3027310b300906035504061302444531183016060355040a130f4e4f47415050532050726f6a65637430820122300d06092a864886f70d01010105000382010f003082010a02820101009a8d2a5336b0eaaad89ce447828c7753b157459b79e3215dc962ca48f58c2cd7650df67d2dd7bda0880c682791f32b35c504e43e77b43c3e4e541f86e35a8293a54fb46e6b16af54d3a4eda458f1a7c8bc1b7479861ca7043337180e40079d9cdccb7e051ada9b6c88c9ec635541e2ebf0842521c3024c826f6fd6db6fd117c74e859d5af4db04448965ab5469b71ce719939a06ef30580f50febf96c474a7d265bb63f86a822ff7b643de6b76e966a18553c2858416cf3309dd24278374bdd82b4404ef6f7f122cec93859351fc6e5ea947e3ceb9d67374fe970e593e5cd05c905e1d24f5a5484f4aadef766e498adf64f7cf04bddd602ae8137b6eea40722d0203010001a321301f301d0603551d0e04160414110b7aa9ebc840b20399f69a431f4dba6ac42a64300d06092a864886f70d01010b0500038201010007c32ad893349cf86952fb5a49cfdc9b13f5e3c800aece77b2e7e0e9c83e34052f140f357ec7e6f4b432dc1ed542218a14835acd2df2deea7efd3fd5e8f1c34e1fb39ec6a427c6e6f4178b609b369040ac1f8844b789f3694dc640de06e44b247afed11637173f36f5886170fafd74954049858c6096308fc93c1bc4dd5685fa7a1f982a422f2a3b36baa8c9500474cf2af91c39cbec1bc898d10194d368aa5e91f1137ec115087c31962d8f76cd120d28c249cf76f4c70f5baa08c70a7234ce4123be080cee789477401965cfe537b924ef36747e8caca62dfefdd1a6288dcb1c4fd2aaa6131a7ad254e9742022cfd597d2ca5c660ce9e41ff537e5a4041e37"; // NOCHECKSTYLE LineLength - private static final String UPSTREAM_CERT = "308204e1308202c9a0030201020204483450fa300d06092a864886f70d01010b050030213110300e060355040b1307462d44726f6964310d300b06035504031304736f7661301e170d3136303832333133333131365a170d3434303130393133333131365a30213110300e060355040b1307462d44726f6964310d300b06035504031304736f766130820222300d06092a864886f70d01010105000382020f003082020a0282020100dfdcd120f3ab224999dddf4ea33ea588d295e4d7130bef48c143e9d76e5c0e0e9e5d45e64208e35feebc79a83f08939dd6a343b7d1e2179930a105a1249ccd36d88ff3feffc6e4dc53dae0163a7876dd45ecc1ddb0adf5099aa56c1a84b52affcd45d0711ffa4de864f35ac0333ebe61ea8673eeda35a88f6af678cc4d0f80b089338ac8f2a8279a64195c611d19445cab3fd1a020afed9bd739bb95142fb2c00a8f847db5ef3325c814f8eb741bacf86ed3907bfe6e4564d2de5895df0c263824e0b75407589bae2d3a4666c13b92102d8781a8ee9bb4a5a1a78c4a9c21efdaf5584da42e84418b28f5a81d0456a3dc5b420991801e6b21e38c99bbe018a5b2d690894a114bc860d35601416aa4dc52216aff8a288d4775cddf8b72d45fd2f87303a8e9c0d67e442530be28eaf139894337266e0b33d57f949256ab32083bcc545bc18a83c9ab8247c12aea037e2b68dee31c734cb1f04f241d3b94caa3a2b258ffaf8e6eae9fbbe029a934dc0a0859c5f120334812693a1c09352340a39f2a678dbc1afa2a978bfee43afefcb7e224a58af2f3d647e5745db59061236b8af6fcfd93b3602f9e456978534f3a7851e800071bf56da80401c81d91c45f82568373af0576b1cc5eef9b85654124b6319770be3cdba3fbebe3715e8918fb6c8966624f3d0e815effac3d2ee06dd34ab9c693218b2c7c06ba99d6b74d4f17b8c3cb0203010001a321301f301d0603551d0e04160414d62bee9f3798509546acc62eb1de14b08b954d4f300d06092a864886f70d01010b05000382020100743f7c5692085895f9d1fffad390fb4202c15f123ed094df259185960fd6dadf66cb19851070f180297bba4e6996a4434616573b375cfee94fee73a4505a7ec29136b7e6c22e6436290e3686fe4379d4e3140ec6a08e70cfd3ed5b634a5eb5136efaaabf5f38e0432d3d79568a556970b8cfba2972f5d23a3856d8a981b9e9bbbbb88f35e708bde9cbc5f681cbd974085b9da28911296fe2579fa64bbe9fa0b93475a7a8db051080b0c5fade0d1c018e7858cd4cbe95145b0620e2f632cbe0f8af9cbf22e2fdaa72245ae31b0877b07181cc69dd2df74454251d8de58d25e76354abe7eb690f22e59b08795a8f2c98c578e0599503d9085927634072c82c9f82abd50fd12b8fd1a9d1954eb5cc0b4cfb5796b5aaec0356643b4a65a368442d92ef94edd3ac6a2b7fe3571b8cf9f462729228aab023ef9183f73792f5379633ccac51079177d604c6bc1873ada6f07d8da6d68c897e88a5fa5d63fdb8df820f46090e0716e7562dd3c140ba279a65b996f60addb0abe29d4bf2f5abe89480771d492307b926d91f02f341b2148502903c43d40f3c6c86a811d060711f0698b384acdcc0add44eb54e42962d3d041accc715afd49407715adc09350cb55e8d9281a3b0b6b5fcd91726eede9b7c8b13afdebb2c2b377629595f1096ba62fb14946dbac5f3c5f0b4e5b712e7acc7dcf6c46cdc5e6d6dfdeee55a0c92c2d70f080ac6"; // NOCHECKSTYLE LineLength - private static final String THIRD_PARTY_CERT = "308204e1308202c9a0030201020204483450fa300d06092a864886f70d01010b050030213110300e060355040b130abcdeabcde012340123400b06035504031304736f7661301e170d3136303832333133333131365a170d3434303130393133333131365a30213110300e060355040b1307462d44726f6964310d300b06035504031304736f766130820222300d06092a864886f70d01010105000382020f003082020a0282020100dfdcd120f3ab224999dddf4ea33ea588d295e4d7130bef48c143e9d76e5c0e0e9e5d45e64208e35feebc79a83f08939dd6a343b7d1e2179930a105a1249ccd36d88ff3feffc6e4dc53dae0163a7876dd45ecc1ddb0adf5099aa56c1a84b52affcd45d0711ffa4de864f35ac0333ebe61ea8673eeda35a88f6af678cc4d0f80b089338ac8f2a8279a64195c611d19445cab3fd1a020afed9bd739bb95142fb2c00a8f847db5ef3325c814f8eb741bacf86ed3907bfe6e4564d2de5895df0c263824e0b75407589bae2d3a4666c13b92102d8781a8ee9bb4a5a1a78c4a9c21efdaf5584da42e84418b28f5a81d0456a3dc5b420991801e6b21e38c99bbe018a5b2d690894a114bc860d35601416aa4dc52216aff8a288d4775cddf8b72d45fd2f87303a8e9c0d67e442530be28eaf139894337266e0b33d57f949256ab32083bcc545bc18a83c9ab8247c12aea037e2b68dee31c734cb1f04f241d3b94caa3a2b258ffaf8e6eae9fbbe029a934dc0a0859c5f120334812693a1c09352340a39f2a678dbc1afa2a978bfee43afefcb7e224a58af2f3d647e5745db59061236b8af6fcfd93b3602f9e456978534f3a7851e800071bf56da80401c81d91c45f82568373af0576b1cc5eef9b85654124b6319770be3cdba3fbebe3715e8918fb6c8966624f3d0e815effac3d2ee06dd34ab9c693218b2c7c06ba99d6b74d4f17b8c3cb0203010001a321301f301d0603551d0e04160414d62bee9f3798509546acc62eb1de14b08b954d4f300d06092a864886f70d01010b05000382020100743f7c5692085895f9d1fffad390fb4202c15f123ed094df259185960fd6dadf66cb19851070f180297bba4e6996a4434616573b375cfee94fee73a4505a7ec29136b7e6c22e6436290e3686fe4379d4e3140ec6a08e70cfd3ed5b634a5eb5136efaaabf5f38e0432d3d79568a556970b8cfba2972f5d23a3856d8a981b9e9bbbbb88f35e708bde9cbc5f681cbd974085b9da28911296fe2579fa64bbe9fa0b93475a7a8db051080b0c5fade0d1c018e7858cd4cbe95145b0620e2f632cbe0f8af9cbf22e2fdaa72245ae31b0877b07181cc69dd2df74454251d8de58d25e76354abe7eb690f22e59b08795a8f2c98c578e0599503d9085927634072c82c9f82abd50fd12b8fd1a9d1954eb5cc0b4cfb5796b5aaec0356643b4a65a368442d92ef94edd3ac6a2b7fe3571b8cf9f462729228aab023ef9183f73792f5379633ccac51079177d604c6bc1873ada6f07d8da6d68c897e88a5fa5d63fdb8df820f46090e0716e7562dd3c140ba279a65b996f60addb0abe29d4bf2f5abe89480771d492307b926d91f02f341b2148502903c43d40f3c6c86a811d060711f0698b384acdcc0add44eb54e42962d3d041accc715afd49407715adc09350cb55e8d9281a3b0b6b5fcd91726eede9b7c8b13afdebb2c2b377629595f1096ba62fb14946dbac5f3c5f0b4e5b712e7acc7dcf6c46cdc5e6d6dfdeee55a0c92c2d70f080ac6"; // NOCHECKSTYLE LineLength - - private static final String FDROID_SIG; - private static final String UPSTREAM_SIG; - private static final String THIRD_PARTY_SIG; - - static { - // Some code requires the full certificate (e.g. when we mock PackageInfo to give to the - // installed app provider), while others requires the hashed certificate (e.g. inserting - // into the apk provider directly, without the need to mock anything). - try { - FDROID_SIG = new Hasher("MD5", FDROID_CERT.getBytes()).getHash(); - UPSTREAM_SIG = new Hasher("MD5", UPSTREAM_CERT.getBytes()).getHash(); - THIRD_PARTY_SIG = new Hasher("MD5", THIRD_PARTY_CERT.getBytes()).getHash(); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException(e); - } - } - @Before public void setup() { TestUtils.registerContentProvider(AppProvider.getAuthority(), AppProvider.class); @@ -61,10 +35,11 @@ public class SuggestedVersionTest extends FDroidProviderTest { @Test public void singleRepoSingleSig() { - App singleApp = insertApp(context, "single.app", "Single App (with beta)", 2, "https://beta.simple.repo"); - insertApk(context, singleApp, 1, FDROID_SIG); - insertApk(context, singleApp, 2, FDROID_SIG); - insertApk(context, singleApp, 3, FDROID_SIG); + App singleApp = TestUtils.insertApp( + context, "single.app", "Single App (with beta)", 2, "https://beta.simple.repo"); + TestUtils.insertApk(context, singleApp, 1, TestUtils.FDROID_SIG); + TestUtils.insertApk(context, singleApp, 2, TestUtils.FDROID_SIG); + TestUtils.insertApk(context, singleApp, 3, TestUtils.FDROID_SIG); assertSuggested("single.app", 2); // By enabling unstable updates, the "upstreamVersionCode" should get ignored, and we should @@ -75,15 +50,15 @@ public class SuggestedVersionTest extends FDroidProviderTest { @Test public void singleRepoMultiSig() { - App unrelatedApp = insertApp(context, "noisy.app", "Noisy App", 3, "https://simple.repo"); - insertApk(context, unrelatedApp, 3, FDROID_SIG); + App unrelatedApp = TestUtils.insertApp(context, "noisy.app", "Noisy App", 3, "https://simple.repo"); + TestUtils.insertApk(context, unrelatedApp, 3, TestUtils.FDROID_SIG); - App singleApp = insertApp(context, "single.app", "Single App", 4, "https://simple.repo"); - insertApk(context, singleApp, 1, FDROID_SIG); - insertApk(context, singleApp, 2, FDROID_SIG); - insertApk(context, singleApp, 3, FDROID_SIG); - insertApk(context, singleApp, 4, UPSTREAM_SIG); - insertApk(context, singleApp, 5, UPSTREAM_SIG); + App singleApp = TestUtils.insertApp(context, "single.app", "Single App", 4, "https://simple.repo"); + TestUtils.insertApk(context, singleApp, 1, TestUtils.FDROID_SIG); + TestUtils.insertApk(context, singleApp, 2, TestUtils.FDROID_SIG); + TestUtils.insertApk(context, singleApp, 3, TestUtils.FDROID_SIG); + TestUtils.insertApk(context, singleApp, 4, TestUtils.UPSTREAM_SIG); + TestUtils.insertApk(context, singleApp, 5, TestUtils.UPSTREAM_SIG); // Given we aren't installed yet, we don't care which signature. // Just get as close to upstreamVersionCode as possible. @@ -91,38 +66,39 @@ public class SuggestedVersionTest extends FDroidProviderTest { // Now install v1 with the f-droid signature. In response, we should only suggest // apps with that sig in the future. That is, version 4 from upstream is not considered. - InstalledAppTestUtils.install(context, "single.app", 1, "v1", FDROID_CERT); - assertSuggested("single.app", 3, FDROID_SIG, 1); + InstalledAppTestUtils.install(context, "single.app", 1, "v1", TestUtils.FDROID_CERT); + assertSuggested("single.app", 3, TestUtils.FDROID_SIG, 1); // This adds the "upstreamVersionCode" version of the app, but signed by f-droid. - insertApk(context, singleApp, 4, FDROID_SIG); - insertApk(context, singleApp, 5, FDROID_SIG); - assertSuggested("single.app", 4, FDROID_SIG, 1); + TestUtils.insertApk(context, singleApp, 4, TestUtils.FDROID_SIG); + TestUtils.insertApk(context, singleApp, 5, TestUtils.FDROID_SIG); + assertSuggested("single.app", 4, TestUtils.FDROID_SIG, 1); // Version 5 from F-Droid is not the "upstreamVersionCode", but with beta updates it should // still become the suggested version now. Preferences.get().setUnstableUpdates(true); - assertSuggested("single.app", 5, FDROID_SIG, 1); + assertSuggested("single.app", 5, TestUtils.FDROID_SIG, 1); } @Test public void multiRepoMultiSig() { - App unrelatedApp = insertApp(context, "noisy.app", "Noisy App", 3, "https://simple.repo"); - insertApk(context, unrelatedApp, 3, FDROID_SIG); + App unrelatedApp = TestUtils.insertApp(context, "noisy.app", "Noisy App", 3, "https://simple.repo"); + TestUtils.insertApk(context, unrelatedApp, 3, TestUtils.FDROID_SIG); - App mainApp = insertApp(context, "single.app", "Single App (Main repo)", 4, "https://main.repo"); - App thirdPartyApp = insertApp(context, "single.app", "Single App (3rd party)", 4, "https://3rd-party.repo"); + App mainApp = TestUtils.insertApp(context, "single.app", "Single App (Main repo)", 4, "https://main.repo"); + App thirdPartyApp = TestUtils.insertApp( + context, "single.app", "Single App (3rd party)", 4, "https://3rd-party.repo"); - insertApk(context, mainApp, 1, FDROID_SIG); - insertApk(context, mainApp, 2, FDROID_SIG); - insertApk(context, mainApp, 3, FDROID_SIG); - insertApk(context, mainApp, 4, UPSTREAM_SIG); - insertApk(context, mainApp, 5, UPSTREAM_SIG); + TestUtils.insertApk(context, mainApp, 1, TestUtils.FDROID_SIG); + TestUtils.insertApk(context, mainApp, 2, TestUtils.FDROID_SIG); + TestUtils.insertApk(context, mainApp, 3, TestUtils.FDROID_SIG); + TestUtils.insertApk(context, mainApp, 4, TestUtils.UPSTREAM_SIG); + TestUtils.insertApk(context, mainApp, 5, TestUtils.UPSTREAM_SIG); - insertApk(context, thirdPartyApp, 3, THIRD_PARTY_SIG); - insertApk(context, thirdPartyApp, 4, THIRD_PARTY_SIG); - insertApk(context, thirdPartyApp, 5, THIRD_PARTY_SIG); - insertApk(context, thirdPartyApp, 6, THIRD_PARTY_SIG); + TestUtils.insertApk(context, thirdPartyApp, 3, TestUtils.THIRD_PARTY_SIG); + TestUtils.insertApk(context, thirdPartyApp, 4, TestUtils.THIRD_PARTY_SIG); + TestUtils.insertApk(context, thirdPartyApp, 5, TestUtils.THIRD_PARTY_SIG); + TestUtils.insertApk(context, thirdPartyApp, 6, TestUtils.THIRD_PARTY_SIG); // Given we aren't installed yet, we don't care which signature or even which repo. // Just get as close to upstreamVersionCode as possible. @@ -130,46 +106,46 @@ public class SuggestedVersionTest extends FDroidProviderTest { // Now install v1 with the f-droid signature. In response, we should only suggest // apps with that sig in the future. That is, version 4 from upstream is not considered. - InstalledAppTestUtils.install(context, "single.app", 1, "v1", FDROID_CERT); - assertSuggested("single.app", 3, FDROID_SIG, 1); + InstalledAppTestUtils.install(context, "single.app", 1, "v1", TestUtils.FDROID_CERT); + assertSuggested("single.app", 3, TestUtils.FDROID_SIG, 1); // This adds the "upstreamVersionCode" version of the app, but signed by f-droid. - insertApk(context, mainApp, 4, FDROID_SIG); - insertApk(context, mainApp, 5, FDROID_SIG); - assertSuggested("single.app", 4, FDROID_SIG, 1); + TestUtils.insertApk(context, mainApp, 4, TestUtils.FDROID_SIG); + TestUtils.insertApk(context, mainApp, 5, TestUtils.FDROID_SIG); + assertSuggested("single.app", 4, TestUtils.FDROID_SIG, 1); // Uninstalling the F-Droid build and installing v3 of the third party means we can now go // back to suggesting version 4. InstalledAppProviderService.deleteAppFromDb(context, "single.app"); - InstalledAppTestUtils.install(context, "single.app", 3, "v3", THIRD_PARTY_CERT); - assertSuggested("single.app", 4, THIRD_PARTY_SIG, 3); + InstalledAppTestUtils.install(context, "single.app", 3, "v3", TestUtils.THIRD_PARTY_CERT); + assertSuggested("single.app", 4, TestUtils.THIRD_PARTY_SIG, 3); // Version 6 from the 3rd party repo is not the "upstreamVersionCode", but with beta updates // it should still become the suggested version now. Preferences.get().setUnstableUpdates(true); - assertSuggested("single.app", 6, THIRD_PARTY_SIG, 3); + assertSuggested("single.app", 6, TestUtils.THIRD_PARTY_SIG, 3); } /** - * This is specifically for the {@link AppProvider.Helper#findCanUpdate(Context, String[])} method used by - * the {@link org.fdroid.fdroid.UpdateService#showAppUpdatesNotification(List)} method. We need to ensure - * that we don't prompt people to update to the wrong sig after an update. + * This is specifically for the {@link AppProvider.Helper#findCanUpdate(android.content.Context, String[])} + * method used by the {@link org.fdroid.fdroid.UpdateService#showAppUpdatesNotification(List)} method. + * We need to ensure that we don't prompt people to update to the wrong sig after an update. */ @Test public void dontSuggestUpstreamVersions() { // By setting the "upstreamVersionCode" to 0, we are letting F-Droid choose the highest compatible version. - App mainApp = insertApp(context, "single.app", "Single App (Main repo)", 0, "https://main.repo"); + App mainApp = TestUtils.insertApp(context, "single.app", "Single App (Main repo)", 0, "https://main.repo"); - insertApk(context, mainApp, 1, FDROID_SIG); - insertApk(context, mainApp, 2, FDROID_SIG); - insertApk(context, mainApp, 3, FDROID_SIG); - insertApk(context, mainApp, 4, FDROID_SIG); - insertApk(context, mainApp, 5, FDROID_SIG); + TestUtils.insertApk(context, mainApp, 1, TestUtils.FDROID_SIG); + TestUtils.insertApk(context, mainApp, 2, TestUtils.FDROID_SIG); + TestUtils.insertApk(context, mainApp, 3, TestUtils.FDROID_SIG); + TestUtils.insertApk(context, mainApp, 4, TestUtils.FDROID_SIG); + TestUtils.insertApk(context, mainApp, 5, TestUtils.FDROID_SIG); - insertApk(context, mainApp, 4, UPSTREAM_SIG); - insertApk(context, mainApp, 5, UPSTREAM_SIG); - insertApk(context, mainApp, 6, UPSTREAM_SIG); - insertApk(context, mainApp, 7, UPSTREAM_SIG); + TestUtils.insertApk(context, mainApp, 4, TestUtils.UPSTREAM_SIG); + TestUtils.insertApk(context, mainApp, 5, TestUtils.UPSTREAM_SIG); + TestUtils.insertApk(context, mainApp, 6, TestUtils.UPSTREAM_SIG); + TestUtils.insertApk(context, mainApp, 7, TestUtils.UPSTREAM_SIG); // If the user was to manually install the app, they should be suggested version 7 from upstream... assertSuggested("single.app", 7); @@ -178,13 +154,13 @@ public class SuggestedVersionTest extends FDroidProviderTest { assertEquals(Collections.EMPTY_LIST, AppProvider.Helper.findCanUpdate(context, Cols.ALL)); // After installing an early F-Droid version, we should then suggest the latest F-Droid version. - InstalledAppTestUtils.install(context, "single.app", 2, "v2", FDROID_CERT); - assertSuggested("single.app", 5, FDROID_SIG, 2); + InstalledAppTestUtils.install(context, "single.app", 2, "v2", TestUtils.FDROID_CERT); + assertSuggested("single.app", 5, TestUtils.FDROID_SIG, 2); // However once we've reached the maximum F-Droid version, then we should not suggest higher versions // with different signatures. InstalledAppProviderService.deleteAppFromDb(context, "single.app"); - InstalledAppTestUtils.install(context, "single.app", 5, "v5", FDROID_CERT); + InstalledAppTestUtils.install(context, "single.app", 5, "v5", TestUtils.FDROID_CERT); assertEquals(Collections.EMPTY_LIST, AppProvider.Helper.findCanUpdate(context, Cols.ALL)); } @@ -230,28 +206,4 @@ public class SuggestedVersionTest extends FDroidProviderTest { } } - private void insertApk(Context context, App app, int versionCode, String signature) { - ContentValues values = new ContentValues(); - values.put(Schema.ApkTable.Cols.SIGNATURE, signature); - Assert.insertApk(context, app, versionCode, values); - } - - private App insertApp(Context context, String packageName, String appName, int upstreamVersionCode, - String repoUrl) { - Repo repo = ensureRepo(context, repoUrl); - ContentValues values = new ContentValues(); - values.put(Cols.REPO_ID, repo.getId()); - values.put(Cols.UPSTREAM_VERSION_CODE, upstreamVersionCode); - return Assert.insertApp(context, packageName, appName, values); - } - - private Repo ensureRepo(Context context, String repoUrl) { - Repo existing = RepoProvider.Helper.findByAddress(context, repoUrl); - if (existing != null) { - return existing; - } - - return RepoProviderTest.insertRepo(context, repoUrl, "", "", ""); - } - } From 6b42b802b397d286d15ae731cee789a84b036c54 Mon Sep 17 00:00:00 2001 From: Peter Serwylo <peter@serwylo.com> Date: Tue, 20 Jun 2017 11:03:58 +1000 Subject: [PATCH 2/7] Tests for preferred multi sig choice. When a single repo provides apks with multiple signatures, then we need to be able to select the preferred one. This adds tests for this which fail, because that feature has not yet been implemented. --- .../java/org/fdroid/fdroid/TestUtils.java | 30 ++ .../fdroid/data/PreferredSignatureTest.java | 279 ++++++++++++++++++ 2 files changed, 309 insertions(+) create mode 100644 app/src/test/java/org/fdroid/fdroid/data/PreferredSignatureTest.java diff --git a/app/src/test/java/org/fdroid/fdroid/TestUtils.java b/app/src/test/java/org/fdroid/fdroid/TestUtils.java index 11fd71c9c..1ae1179e6 100644 --- a/app/src/test/java/org/fdroid/fdroid/TestUtils.java +++ b/app/src/test/java/org/fdroid/fdroid/TestUtils.java @@ -24,6 +24,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.security.NoSuchAlgorithmException; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; @@ -54,6 +55,27 @@ public class TestUtils { } } + private static String formatSigForDebugging(String sig) { + String suffix; + + // Can't use a switch statement here because *_SIG is not a constant, despite beign static final. + if (sig.equals(FDROID_SIG)) { + suffix = "F-Droid"; + } else if (sig.equals(UPSTREAM_SIG)) { + suffix = "Upstream"; + } else if (sig.equals(THIRD_PARTY_SIG)) { + suffix = "3rd Party"; + } else { + suffix = "Unknown"; + } + + return sig + " [" + suffix + "]"; + } + + public static void assertSignaturesMatch(String message, String expected, String actual) { + assertEquals(message, formatSigForDebugging(expected), formatSigForDebugging(actual)); + } + public static void insertApk(Context context, App app, int versionCode, String signature) { ContentValues values = new ContentValues(); values.put(Schema.ApkTable.Cols.SIGNATURE, signature); @@ -69,6 +91,14 @@ public class TestUtils { return Assert.insertApp(context, packageName, appName, values); } + public static App insertApp(Context context, String packageName, String appName, int upstreamVersionCode, + Repo repo) { + ContentValues values = new ContentValues(); + values.put(Schema.AppMetadataTable.Cols.REPO_ID, repo.getId()); + values.put(Schema.AppMetadataTable.Cols.UPSTREAM_VERSION_CODE, upstreamVersionCode); + return Assert.insertApp(context, packageName, appName, values); + } + public static Repo ensureRepo(Context context, String repoUrl) { Repo existing = RepoProvider.Helper.findByAddress(context, repoUrl); if (existing != null) { diff --git a/app/src/test/java/org/fdroid/fdroid/data/PreferredSignatureTest.java b/app/src/test/java/org/fdroid/fdroid/data/PreferredSignatureTest.java new file mode 100644 index 000000000..6b48b3e97 --- /dev/null +++ b/app/src/test/java/org/fdroid/fdroid/data/PreferredSignatureTest.java @@ -0,0 +1,279 @@ +package org.fdroid.fdroid.data; + +import android.app.Application; +import android.content.Context; + +import org.fdroid.fdroid.BuildConfig; +import org.fdroid.fdroid.Preferences; +import org.fdroid.fdroid.TestUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import static org.junit.Assert.assertEquals; + +@Config(constants = BuildConfig.class, application = Application.class, sdk = 24) +@RunWith(RobolectricTestRunner.class) +public class PreferredSignatureTest extends FDroidProviderTest { + + private static final String PACKAGE_NAME = "app.example.com"; + + @Before + public void setup() { + TestUtils.registerContentProvider(AppProvider.getAuthority(), AppProvider.class); + Preferences.setup(context); + } + + @After + public void tearDown() { + Preferences.clearSingletonForTesting(); + } + + private Repo createFDroidRepo() { + return RepoProviderTest.insertRepo(context, "https://f-droid.org/fdroid/repo", "", "", ""); + } + + private App populateFDroidRepo(Repo repo) { + App app = TestUtils.insertApp(context, PACKAGE_NAME, "App", 3100, repo); + + TestUtils.insertApk(context, app, 1100, TestUtils.FDROID_SIG); // 1.0 + TestUtils.insertApk(context, app, 2100, TestUtils.FDROID_SIG); // 2.0 + TestUtils.insertApk(context, app, 3100, TestUtils.FDROID_SIG); // 3.0 + + TestUtils.insertApk(context, app, 2100, TestUtils.UPSTREAM_SIG); // 2.0 + TestUtils.insertApk(context, app, 3100, TestUtils.UPSTREAM_SIG); // 3.0 + + return app; + } + + private Repo createDevRepo() { + return RepoProviderTest.insertRepo(context, "https://dev.upstream.com/fdroid/repo", "", "", ""); + } + + private App populateDevRepo(Repo repo) { + App app = TestUtils.insertApp(context, PACKAGE_NAME, "App", 4100, repo); + + TestUtils.insertApk(context, app, 1001, TestUtils.THIRD_PARTY_SIG); // 1.0-rc2 + TestUtils.insertApk(context, app, 1100, TestUtils.THIRD_PARTY_SIG); // 1.0 + TestUtils.insertApk(context, app, 2001, TestUtils.THIRD_PARTY_SIG); // 2.0-rc1 + TestUtils.insertApk(context, app, 2002, TestUtils.THIRD_PARTY_SIG); // 2.0-rc2 + TestUtils.insertApk(context, app, 2100, TestUtils.THIRD_PARTY_SIG); // 2.0 + TestUtils.insertApk(context, app, 3001, TestUtils.THIRD_PARTY_SIG); // 3.0-rc1 + TestUtils.insertApk(context, app, 3100, TestUtils.THIRD_PARTY_SIG); // 3.0 + TestUtils.insertApk(context, app, 4001, TestUtils.THIRD_PARTY_SIG); // 4.0-rc1 + TestUtils.insertApk(context, app, 4002, TestUtils.THIRD_PARTY_SIG); // 4.0-rc2 + TestUtils.insertApk(context, app, 4100, TestUtils.THIRD_PARTY_SIG); // 4.0 + TestUtils.insertApk(context, app, 5001, TestUtils.THIRD_PARTY_SIG); // 5.0-rc1 + TestUtils.insertApk(context, app, 5002, TestUtils.THIRD_PARTY_SIG); // 5.0-rc2 + TestUtils.insertApk(context, app, 5003, TestUtils.THIRD_PARTY_SIG); // 5.0-rc3 + + return app; + } + + private Repo createUpstreamRepo() { + return RepoProviderTest.insertRepo(context, "https://upstream.com/fdroid/repo", "", "", ""); + } + + private App populateUpstreamRepo(Repo repo) { + App app = TestUtils.insertApp(context, PACKAGE_NAME, "App", 4100, repo); + + TestUtils.insertApk(context, app, 2100, TestUtils.UPSTREAM_SIG); + TestUtils.insertApk(context, app, 3100, TestUtils.UPSTREAM_SIG); + TestUtils.insertApk(context, app, 4100, TestUtils.UPSTREAM_SIG); + + return app; + } + + @Test + public void onlyFDroid() { + populateFDroidRepo(createFDroidRepo()); + assertSuggested(context, 3100, TestUtils.UPSTREAM_SIG); + } + + /** + * @see #assertFdroidThenDev() + */ + @Test + public void fdroidThenDev1() { + Repo fdroid = createFDroidRepo(); + Repo dev = createDevRepo(); + + populateFDroidRepo(fdroid); + populateDevRepo(dev); + + assertFdroidThenDev(); + } + + /** + * @see #assertFdroidThenDev() + */ + @Test + public void fdroidThenDev2() { + Repo fdroid = createFDroidRepo(); + Repo dev = createDevRepo(); + + populateDevRepo(dev); + populateFDroidRepo(fdroid); + + assertFdroidThenDev(); + } + + /** + * Both {@link #fdroidThenDev1()} and {@link #fdroidThenDev2()} add the same repos, with the same priorities and + * the same apps/apks. The only difference is in the order with which they get added to the database. They both + * then delegate here and assert that everything works as expected. The reason for testing like this is to ensure + * that the order of rows in the database has no bearing on the correct suggestions of signatures. + * @see #fdroidThenDev1() + * @see #fdroidThenDev2() + */ + private void assertFdroidThenDev() { + assertSuggested(context, 4100, TestUtils.THIRD_PARTY_SIG); + + Preferences.get().setUnstableUpdates(true); + assertSuggested(context, 5003, TestUtils.THIRD_PARTY_SIG); + + Preferences.get().setUnstableUpdates(false); + assertSuggested(context, 4100, TestUtils.THIRD_PARTY_SIG); + } + + /** + * @see #assertFdroidThenUpstream() + */ + @Test + public void fdroidThenUpstream1() { + Repo fdroid = createFDroidRepo(); + Repo upstream = createUpstreamRepo(); + + populateUpstreamRepo(upstream); + populateFDroidRepo(fdroid); + + assertFdroidThenUpstream(); + } + + /** + * @see #assertFdroidThenUpstream() + */ + @Test + public void fdroidThenUpstream2() { + Repo fdroid = createFDroidRepo(); + Repo upstream = createUpstreamRepo(); + + populateFDroidRepo(fdroid); + populateUpstreamRepo(upstream); + + assertFdroidThenUpstream(); + } + + /** + * @see #fdroidThenUpstream1() + * @see #fdroidThenUpstream2() + * @see #assertFdroidThenDev() + */ + private void assertFdroidThenUpstream() { + assertSuggested(context, 4100, TestUtils.UPSTREAM_SIG); + } + + /** + * @see #assertFdroidThenUpstreamThenDev() + */ + @Test + public void fdroidThenUpstreamThenDev1() { + Repo fdroid = createFDroidRepo(); + Repo upstream = createUpstreamRepo(); + Repo dev = createDevRepo(); + + populateFDroidRepo(fdroid); + populateUpstreamRepo(upstream); + populateDevRepo(dev); + + assertFdroidThenUpstreamThenDev(); + } + + /** + * @see #assertFdroidThenUpstreamThenDev() + */ + @Test + public void fdroidThenUpstreamThenDev2() { + Repo fdroid = createFDroidRepo(); + Repo upstream = createUpstreamRepo(); + Repo dev = createDevRepo(); + + populateDevRepo(dev); + populateUpstreamRepo(upstream); + populateFDroidRepo(fdroid); + + assertFdroidThenUpstreamThenDev(); + } + + /** + * @see #fdroidThenUpstreamThenDev1() + * @see #fdroidThenUpstreamThenDev2() + * @see #assertFdroidThenDev() + */ + private void assertFdroidThenUpstreamThenDev() { + assertSuggested(context, 4100, TestUtils.THIRD_PARTY_SIG); + + Preferences.get().setUnstableUpdates(true); + assertSuggested(context, 5003, TestUtils.THIRD_PARTY_SIG); + + Preferences.get().setUnstableUpdates(false); + assertSuggested(context, 4100, TestUtils.THIRD_PARTY_SIG); + } + + /** + * @see #assertFdroidThenDevThenUpstream() + */ + @Test + public void fdroidThenDevThenUpstream1() { + Repo fdroid = createFDroidRepo(); + Repo dev = createDevRepo(); + Repo upstream = createUpstreamRepo(); + + populateFDroidRepo(fdroid); + populateDevRepo(dev); + populateUpstreamRepo(upstream); + + assertFdroidThenDevThenUpstream(); + } + + /** + * @see #assertFdroidThenDevThenUpstream() + */ + @Test + public void fdroidThenDevThenUpstream2() { + Repo fdroid = createFDroidRepo(); + Repo dev = createDevRepo(); + Repo upstream = createUpstreamRepo(); + + populateFDroidRepo(fdroid); + populateDevRepo(dev); + populateUpstreamRepo(upstream); + + assertFdroidThenDevThenUpstream(); + } + + /** + * @see #fdroidThenDevThenUpstream1() + * @see #fdroidThenDevThenUpstream2() + * @see #assertFdroidThenDev() + */ + private void assertFdroidThenDevThenUpstream() { + assertSuggested(context, 4100, TestUtils.UPSTREAM_SIG); + } + + private void assertSuggested(Context context, int suggestedVersion, String suggestedSig) { + AppProvider.Helper.calcSuggestedApks(context); + AppProvider.Helper.recalculatePreferredMetadata(context); + + App suggestedApp = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(), PACKAGE_NAME); + assertEquals("Suggested version on App", suggestedVersion, suggestedApp.suggestedVersionCode); + + Apk suggestedApk = ApkProvider.Helper.findSuggestedApk(context, suggestedApp); + assertEquals("Version on suggested Apk", suggestedVersion, suggestedApk.versionCode); + TestUtils.assertSignaturesMatch("Signature on suggested Apk", suggestedSig, suggestedApk.sig); + } + +} From caac895442adb6178295c0992c2f207cf08da43a Mon Sep 17 00:00:00 2001 From: Peter Serwylo <peter@serwylo.com> Date: Wed, 21 Jun 2017 15:48:21 +1000 Subject: [PATCH 3/7] Add "preferredSigner" field to App. At present, this is chosen from the first package in the index-v1 metadata. --- .../java/org/fdroid/fdroid/IndexV1Updater.java | 7 ++++++- .../org/fdroid/fdroid/data/ApkProvider.java | 17 ++++++++++++++++- .../main/java/org/fdroid/fdroid/data/App.java | 8 ++++++++ .../java/org/fdroid/fdroid/data/DBHelper.java | 15 ++++++++++++++- .../java/org/fdroid/fdroid/data/Schema.java | 5 +++-- .../test/java/org/fdroid/fdroid/TestUtils.java | 3 ++- .../fdroid/data/PreferredSignatureTest.java | 6 +++--- .../fdroid/updater/IndexV1UpdaterTest.java | 1 + 8 files changed, 53 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/IndexV1Updater.java b/app/src/main/java/org/fdroid/fdroid/IndexV1Updater.java index 7b4d36a5e..a8de65bb9 100644 --- a/app/src/main/java/org/fdroid/fdroid/IndexV1Updater.java +++ b/app/src/main/java/org/fdroid/fdroid/IndexV1Updater.java @@ -217,9 +217,14 @@ public class IndexV1Updater extends RepoUpdater { if (packages != null) { apks = packages.get(app.packageName); } + if (apks == null) { Log.i(TAG, "processIndexV1 empty packages"); - apks = new ArrayList<Apk>(0); + apks = new ArrayList<>(0); + } + + if (apks.size() > 0) { + app.preferredSigner = apks.get(0).sig; } if (appCount % 50 == 0) { diff --git a/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java b/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java index e73cdfa2e..147683b18 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java @@ -8,6 +8,7 @@ import android.database.Cursor; import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.text.TextUtils; import android.util.Log; import org.fdroid.fdroid.data.Schema.ApkTable; @@ -75,8 +76,22 @@ public class ApkProvider extends FDroidProvider { return resolver.delete(uri, null, null); } + /** + * Find an app which is closest to the version code suggested by the server, with some caveates: + * <ul> + * <li>If installed, limit to apks signed by the same signer as the installed apk.</li> + * <li>Otherwise, limit to apks signed by the "preferred" signer (see {@link App#preferredSigner}).</li> + * </ul> + */ public static Apk findSuggestedApk(Context context, App app) { - return findApkFromAnyRepo(context, app.packageName, app.suggestedVersionCode, app.installedSig); + String preferredSignature = null; + if (!TextUtils.isEmpty(app.installedSig)) { + preferredSignature = app.installedSig; + } else if (!TextUtils.isEmpty(app.preferredSigner)) { + preferredSignature = app.preferredSigner; + } + + return findApkFromAnyRepo(context, app.packageName, app.suggestedVersionCode, preferredSignature); } public static Apk findApkFromAnyRepo(Context context, String packageName, int versionCode) { 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 ad189b7b0..8faeaeceb 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/App.java +++ b/app/src/main/java/org/fdroid/fdroid/data/App.java @@ -98,6 +98,8 @@ public class App extends ValueObject implements Comparable<App>, Parcelable { private long id; @JsonIgnore private AppPrefs prefs; + @JsonIgnore + public String preferredSigner; @JacksonInject("repoId") public long repoId; @@ -286,6 +288,9 @@ public class App extends ValueObject implements Comparable<App>, Parcelable { case Cols.SuggestedApk.VERSION_NAME: suggestedVersionName = cursor.getString(i); break; + case Cols.PREFERRED_SIGNER: + preferredSigner = cursor.getString(i); + break; case Cols.SUGGESTED_VERSION_CODE: suggestedVersionCode = cursor.getInt(i); break; @@ -828,6 +833,7 @@ public class App extends ValueObject implements Comparable<App>, Parcelable { values.put(Cols.FLATTR_ID, flattrID); values.put(Cols.ADDED, Utils.formatDate(added, "")); values.put(Cols.LAST_UPDATED, Utils.formatDate(lastUpdated, "")); + values.put(Cols.PREFERRED_SIGNER, preferredSigner); values.put(Cols.SUGGESTED_VERSION_CODE, suggestedVersionCode); values.put(Cols.UPSTREAM_VERSION_NAME, upstreamVersionName); values.put(Cols.UPSTREAM_VERSION_CODE, upstreamVersionCode); @@ -1003,6 +1009,7 @@ public class App extends ValueObject implements Comparable<App>, Parcelable { dest.writeString(this.bitcoin); dest.writeString(this.litecoin); dest.writeString(this.flattrID); + dest.writeString(this.preferredSigner); dest.writeString(this.upstreamVersionName); dest.writeInt(this.upstreamVersionCode); dest.writeString(this.suggestedVersionName); @@ -1050,6 +1057,7 @@ public class App extends ValueObject implements Comparable<App>, Parcelable { this.bitcoin = in.readString(); this.litecoin = in.readString(); this.flattrID = in.readString(); + this.preferredSigner = in.readString(); this.upstreamVersionName = in.readString(); this.upstreamVersionCode = in.readInt(); this.suggestedVersionName = in.readString(); 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 555d39f90..3ad0ee818 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java +++ b/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java @@ -128,6 +128,7 @@ class DBHelper extends SQLiteOpenHelper { + AppMetadataTable.Cols.SOURCE_CODE + " text, " + AppMetadataTable.Cols.VIDEO + " string, " + AppMetadataTable.Cols.CHANGELOG + " text, " + + AppMetadataTable.Cols.PREFERRED_SIGNER + " text," + AppMetadataTable.Cols.SUGGESTED_VERSION_CODE + " text," + AppMetadataTable.Cols.UPSTREAM_VERSION_NAME + " text," + AppMetadataTable.Cols.UPSTREAM_VERSION_CODE + " integer," @@ -192,7 +193,7 @@ class DBHelper extends SQLiteOpenHelper { + InstalledAppTable.Cols.HASH + " TEXT NOT NULL" + " );"; - protected static final int DB_VERSION = 71; + protected static final int DB_VERSION = 72; private final Context context; @@ -278,6 +279,18 @@ class DBHelper extends SQLiteOpenHelper { addWhatsNewAndVideo(db, oldVersion); dropApkPrimaryKey(db, oldVersion); addIntegerPrimaryKeyToInstalledApps(db, oldVersion); + addPreferredSignerToApp(db, oldVersion); + } + + private void addPreferredSignerToApp(SQLiteDatabase db, int oldVersion) { + if (oldVersion >= 72) { + return; + } + + if (!columnExists(db, AppMetadataTable.NAME, AppMetadataTable.Cols.PREFERRED_SIGNER)) { + Log.i(TAG, "Adding preferred signer to app table."); + db.execSQL("alter table " + AppMetadataTable.NAME + " add column " + AppMetadataTable.Cols.PREFERRED_SIGNER + " text;"); + } } private void addIntegerPrimaryKeyToInstalledApps(SQLiteDatabase db, int oldVersion) { 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 cb02a85d9..c10996b7c 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/Schema.java +++ b/app/src/main/java/org/fdroid/fdroid/data/Schema.java @@ -136,6 +136,7 @@ public interface Schema { String BITCOIN = "bitcoinAddr"; String LITECOIN = "litecoinAddr"; String FLATTR_ID = "flattrID"; + String PREFERRED_SIGNER = "preferredSigner"; String SUGGESTED_VERSION_CODE = "suggestedVercode"; String UPSTREAM_VERSION_NAME = "upstreamVersion"; String UPSTREAM_VERSION_CODE = "upstreamVercode"; @@ -192,7 +193,7 @@ public interface Schema { ANTI_FEATURES, REQUIREMENTS, ICON_URL, ICON_URL_LARGE, FEATURE_GRAPHIC, PROMO_GRAPHIC, TV_BANNER, PHONE_SCREENSHOTS, SEVEN_INCH_SCREENSHOTS, TEN_INCH_SCREENSHOTS, TV_SCREENSHOTS, WEAR_SCREENSHOTS, - SUGGESTED_VERSION_CODE, + PREFERRED_SIGNER, SUGGESTED_VERSION_CODE, }; /** @@ -208,7 +209,7 @@ public interface Schema { ANTI_FEATURES, REQUIREMENTS, ICON_URL, ICON_URL_LARGE, FEATURE_GRAPHIC, PROMO_GRAPHIC, TV_BANNER, PHONE_SCREENSHOTS, SEVEN_INCH_SCREENSHOTS, TEN_INCH_SCREENSHOTS, TV_SCREENSHOTS, WEAR_SCREENSHOTS, - SUGGESTED_VERSION_CODE, SuggestedApk.VERSION_NAME, + PREFERRED_SIGNER, SUGGESTED_VERSION_CODE, SuggestedApk.VERSION_NAME, InstalledApp.VERSION_CODE, InstalledApp.VERSION_NAME, InstalledApp.SIGNATURE, Package.PACKAGE_NAME, }; diff --git a/app/src/test/java/org/fdroid/fdroid/TestUtils.java b/app/src/test/java/org/fdroid/fdroid/TestUtils.java index 1ae1179e6..837f35db7 100644 --- a/app/src/test/java/org/fdroid/fdroid/TestUtils.java +++ b/app/src/test/java/org/fdroid/fdroid/TestUtils.java @@ -92,10 +92,11 @@ public class TestUtils { } public static App insertApp(Context context, String packageName, String appName, int upstreamVersionCode, - Repo repo) { + Repo repo, String preferredSigner) { ContentValues values = new ContentValues(); values.put(Schema.AppMetadataTable.Cols.REPO_ID, repo.getId()); values.put(Schema.AppMetadataTable.Cols.UPSTREAM_VERSION_CODE, upstreamVersionCode); + values.put(Schema.AppMetadataTable.Cols.PREFERRED_SIGNER, preferredSigner); return Assert.insertApp(context, packageName, appName, values); } diff --git a/app/src/test/java/org/fdroid/fdroid/data/PreferredSignatureTest.java b/app/src/test/java/org/fdroid/fdroid/data/PreferredSignatureTest.java index 6b48b3e97..20c2d55e3 100644 --- a/app/src/test/java/org/fdroid/fdroid/data/PreferredSignatureTest.java +++ b/app/src/test/java/org/fdroid/fdroid/data/PreferredSignatureTest.java @@ -37,7 +37,7 @@ public class PreferredSignatureTest extends FDroidProviderTest { } private App populateFDroidRepo(Repo repo) { - App app = TestUtils.insertApp(context, PACKAGE_NAME, "App", 3100, repo); + App app = TestUtils.insertApp(context, PACKAGE_NAME, "App", 3100, repo, TestUtils.UPSTREAM_SIG); TestUtils.insertApk(context, app, 1100, TestUtils.FDROID_SIG); // 1.0 TestUtils.insertApk(context, app, 2100, TestUtils.FDROID_SIG); // 2.0 @@ -54,7 +54,7 @@ public class PreferredSignatureTest extends FDroidProviderTest { } private App populateDevRepo(Repo repo) { - App app = TestUtils.insertApp(context, PACKAGE_NAME, "App", 4100, repo); + App app = TestUtils.insertApp(context, PACKAGE_NAME, "App", 4100, repo, TestUtils.THIRD_PARTY_SIG); TestUtils.insertApk(context, app, 1001, TestUtils.THIRD_PARTY_SIG); // 1.0-rc2 TestUtils.insertApk(context, app, 1100, TestUtils.THIRD_PARTY_SIG); // 1.0 @@ -78,7 +78,7 @@ public class PreferredSignatureTest extends FDroidProviderTest { } private App populateUpstreamRepo(Repo repo) { - App app = TestUtils.insertApp(context, PACKAGE_NAME, "App", 4100, repo); + App app = TestUtils.insertApp(context, PACKAGE_NAME, "App", 4100, repo, TestUtils.UPSTREAM_SIG); TestUtils.insertApk(context, app, 2100, TestUtils.UPSTREAM_SIG); TestUtils.insertApk(context, app, 3100, TestUtils.UPSTREAM_SIG); diff --git a/app/src/test/java/org/fdroid/fdroid/updater/IndexV1UpdaterTest.java b/app/src/test/java/org/fdroid/fdroid/updater/IndexV1UpdaterTest.java index d44c63292..10675b127 100644 --- a/app/src/test/java/org/fdroid/fdroid/updater/IndexV1UpdaterTest.java +++ b/app/src/test/java/org/fdroid/fdroid/updater/IndexV1UpdaterTest.java @@ -263,6 +263,7 @@ public class IndexV1UpdaterTest extends FDroidProviderTest { "installedSig", "installedVersionCode", "installedVersionName", + "preferredSigner", "prefs", "TAG", }; From 41f85f3c9df934daba0ee0d60c4c01bb071fa6e7 Mon Sep 17 00:00:00 2001 From: Peter Serwylo <peter@serwylo.com> Date: Fri, 30 Jun 2017 12:14:15 +1000 Subject: [PATCH 4/7] Correctly check for 'suggestedApk' in app details. Take into account the preferred/installed signature instead of just the version code. --- .../org/fdroid/fdroid/data/ApkProvider.java | 10 +--------- .../main/java/org/fdroid/fdroid/data/App.java | 20 +++++++++++++++++++ .../views/AppDetailsRecyclerViewAdapter.java | 3 ++- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java b/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java index 147683b18..d3f3e8ddc 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java @@ -8,7 +8,6 @@ import android.database.Cursor; import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.text.TextUtils; import android.util.Log; import org.fdroid.fdroid.data.Schema.ApkTable; @@ -84,14 +83,7 @@ public class ApkProvider extends FDroidProvider { * </ul> */ public static Apk findSuggestedApk(Context context, App app) { - String preferredSignature = null; - if (!TextUtils.isEmpty(app.installedSig)) { - preferredSignature = app.installedSig; - } else if (!TextUtils.isEmpty(app.preferredSigner)) { - preferredSignature = app.preferredSigner; - } - - return findApkFromAnyRepo(context, app.packageName, app.suggestedVersionCode, preferredSignature); + return findApkFromAnyRepo(context, app.packageName, app.suggestedVersionCode, app.getMostAppropriateSignature()); } public static Apk findApkFromAnyRepo(Context context, String packageName, int versionCode) { 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 8faeaeceb..e8e7621bc 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/App.java +++ b/app/src/main/java/org/fdroid/fdroid/data/App.java @@ -1098,4 +1098,24 @@ public class App extends ValueObject implements Comparable<App>, Parcelable { return new App[size]; } }; + + /** + * Choose the signature which we should encourage the user to install. + * Usually, we want the {@link #preferredSigner} rather than any random signature. + * However, if the app is installed, then we override this and instead want to only encourage + * the user to try and install versions with that signature (because thats all the OS will let + * them do). + * TODO: I don't think preferredSigner should ever be null, because if an app has apks then + * we should have chosen the first and used that. If so, then we should change to @NonNull and + * throw an exception if it is null. + */ + @Nullable + public String getMostAppropriateSignature() { + if (!TextUtils.isEmpty(installedSig)) { + return installedSig; + } else if (!TextUtils.isEmpty(preferredSigner)) { + return preferredSigner; + } + return null; + } } diff --git a/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java b/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java index dc76d1f99..eb7b4cc16 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java +++ b/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java @@ -178,9 +178,10 @@ public class AppDetailsRecyclerViewAdapter private Apk getSuggestedApk() { Apk curApk = null; + String appropriateSig = app.getMostAppropriateSignature(); for (int i = 0; i < versions.size(); i++) { final Apk apk = versions.get(i); - if (apk.versionCode == app.suggestedVersionCode) { + if (apk.versionCode == app.suggestedVersionCode && TextUtils.equals(apk.sig, appropriateSig)) { curApk = apk; break; } From 677fd3a522776ae3e356d9ec78520c3cbd672d5e Mon Sep 17 00:00:00 2001 From: Peter Serwylo <peter@serwylo.com> Date: Fri, 30 Jun 2017 12:23:33 +1000 Subject: [PATCH 5/7] Use signature as well as version code in app details for suggested apk. --- .../fdroid/views/AppDetailsRecyclerViewAdapter.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java b/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java index eb7b4cc16..46bb5cb39 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java +++ b/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java @@ -478,7 +478,7 @@ public class AppDetailsRecyclerViewAdapter if (callbacks.isAppDownloading()) { buttonPrimaryView.setText(R.string.downloading); buttonPrimaryView.setEnabled(false); - } else if (!app.isInstalled() && app.suggestedVersionCode > 0 && versions.size() > 0) { + } else if (!app.isInstalled() && suggestedApk != null) { // Check count > 0 due to incompatible apps resulting in an empty list. callbacks.disableAndroidBeam(); // Set Install button and hide second button @@ -487,7 +487,7 @@ public class AppDetailsRecyclerViewAdapter buttonPrimaryView.setEnabled(true); } else if (app.isInstalled()) { callbacks.enableAndroidBeam(); - if (app.canAndWantToUpdate(context)) { + if (app.canAndWantToUpdate(context) && suggestedApk != null) { buttonPrimaryView.setText(R.string.menu_upgrade); buttonPrimaryView.setOnClickListener(onUpgradeClickListener); } else { @@ -819,9 +819,12 @@ public class AppDetailsRecyclerViewAdapter public void bindModel(final Apk apk) { java.text.DateFormat df = DateFormat.getDateFormat(context); + boolean isSuggested = apk.versionCode == app.suggestedVersionCode && + TextUtils.equals(apk.sig, app.getMostAppropriateSignature()); + version.setText(context.getString(R.string.version) + " " + apk.versionName - + (apk.versionCode == app.suggestedVersionCode ? " ☆" : "")); + + (isSuggested ? " ☆" : "")); status.setText(getInstalledStatus(apk)); From bf4b0d89a1821076dc404d3cba16242dec2a09a6 Mon Sep 17 00:00:00 2001 From: Peter Serwylo <peter@serwylo.com> Date: Fri, 30 Jun 2017 14:21:25 +1000 Subject: [PATCH 6/7] Ensure that the suggestedVersionCode is updated after [un]installing. Previously, it was only done on repo update. Now it is done whenever an app is installed or unisntalled. The query to update the suggested version for each app is quite slow when run at the end of a repo update. However in this change, we are limiting the query to only update a single app, which means that performance should not be a problem. --- .../org/fdroid/fdroid/data/AppProvider.java | 64 +++++++++++++++---- .../fdroid/data/InstalledAppProvider.java | 29 +++++++-- .../org/fdroid/fdroid/data/LoggingQuery.java | 15 +++-- .../java/org/fdroid/fdroid/TestUtils.java | 12 ++++ .../fdroid/data/PreferredSignatureTest.java | 17 ++++- .../fdroid/data/SuggestedVersionTest.java | 17 ++++- 6 files changed, 124 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java b/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java index 7913bddbc..c35669d07 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java @@ -117,6 +117,11 @@ public class AppProvider extends FDroidProvider { return app; } + public static void calcSuggestedApk(Context context, String packageName) { + Uri uri = Uri.withAppendedPath(calcSuggestedApksUri(), packageName); + context.getContentResolver().update(uri, null, null, null); + } + public static void calcSuggestedApks(Context context) { context.getContentResolver().update(calcSuggestedApksUri(), null, null, null); } @@ -385,6 +390,7 @@ public class AppProvider extends FDroidProvider { static { MATCHER.addURI(getAuthority(), null, CODE_LIST); MATCHER.addURI(getAuthority(), PATH_CALC_SUGGESTED_APKS, CALC_SUGGESTED_APKS); + MATCHER.addURI(getAuthority(), PATH_CALC_SUGGESTED_APKS + "/*", CALC_SUGGESTED_APKS); MATCHER.addURI(getAuthority(), PATH_RECENTLY_UPDATED, RECENTLY_UPDATED); MATCHER.addURI(getAuthority(), PATH_CATEGORY + "/*", CATEGORY); MATCHER.addURI(getAuthority(), PATH_SEARCH + "/*/*", SEARCH_TEXT_AND_CATEGORIES); @@ -879,7 +885,13 @@ public class AppProvider extends FDroidProvider { throw new UnsupportedOperationException("Update not supported for " + uri + "."); } - updateSuggestedApks(); + List<String> segments = uri.getPathSegments(); + if (segments.size() > 1) { + String packageName = segments.get(1); + updateSuggestedApk(packageName); + } else { + updateSuggestedApks(); + } getContext().getContentResolver().notifyChange(getCanUpdateUri(), null); return 0; } @@ -887,8 +899,8 @@ public class AppProvider extends FDroidProvider { protected void updateAllAppDetails() { updatePreferredMetadata(); updateCompatibleFlags(); - updateSuggestedFromUpstream(); - updateSuggestedFromLatest(); + updateSuggestedFromUpstream(null); + updateSuggestedFromLatest(null); updateIconUrls(); } @@ -909,8 +921,13 @@ public class AppProvider extends FDroidProvider { * {@link android.app.IntentService} as described in https://gitlab.com/fdroid/fdroidclient/issues/520. */ protected void updateSuggestedApks() { - updateSuggestedFromUpstream(); - updateSuggestedFromLatest(); + updateSuggestedFromUpstream(null); + updateSuggestedFromLatest(null); + } + + protected void updateSuggestedApk(String packageName) { + updateSuggestedFromUpstream(packageName); + updateSuggestedFromLatest(packageName); } private void updatePreferredMetadata() { @@ -964,9 +981,9 @@ public class AppProvider extends FDroidProvider { * If the app is installed, then all apks signed by a different certificate are * ignored for the purpose of this calculation. * - * @see #updateSuggestedFromLatest() + * @see #updateSuggestedFromLatest(String) */ - private void updateSuggestedFromUpstream() { + private void updateSuggestedFromUpstream(@Nullable String packageName) { Utils.debugLog(TAG, "Calculating suggested versions for all NON-INSTALLED apps which specify an upstream version code."); final String apk = getApkTableName(); @@ -976,6 +993,14 @@ public class AppProvider extends FDroidProvider { final boolean unstableUpdates = Preferences.get().getUnstableUpdates(); String restrictToStable = unstableUpdates ? "" : (apk + "." + ApkTable.Cols.VERSION_CODE + " <= " + app + "." + Cols.UPSTREAM_VERSION_CODE + " AND "); + String restrictToApp = ""; + String[] args = null; + + if (packageName != null) { + restrictToApp = " AND " + app + "." + Cols.PACKAGE_ID + " = (" + getPackageIdFromPackageNameQuery() + ") "; + args = new String[]{packageName}; + } + // The join onto `appForThisApk` is to ensure that the MAX(apk.versionCode) is chosen from // all apps regardless of repo. If we joined directly onto the outer `app` table we are // in the process of updating, then it would be limited to only apks from the same repo. @@ -1001,9 +1026,9 @@ public class AppProvider extends FDroidProvider { apk + "." + ApkTable.Cols.SIGNATURE + " = COALESCE(" + installed + "." + InstalledAppTable.Cols.SIGNATURE + ", " + apk + "." + ApkTable.Cols.SIGNATURE + ") AND " + restrictToStable + " ( " + app + "." + Cols.IS_COMPATIBLE + " = 0 OR " + apk + "." + Cols.IS_COMPATIBLE + " = 1 ) ) " + - " WHERE " + Cols.UPSTREAM_VERSION_CODE + " > 0 "; + " WHERE " + Cols.UPSTREAM_VERSION_CODE + " > 0 " + restrictToApp; - LoggingQuery.execSQL(db(), updateSql); + LoggingQuery.execSQL(db(), updateSql, args); } /** @@ -1014,15 +1039,28 @@ public class AppProvider extends FDroidProvider { * out from the upstream vercode. In such a case, fall back to the simpler * algorithm as if upstreamVercode was 0. * - * @see #updateSuggestedFromUpstream() + * @see #updateSuggestedFromUpstream(String) */ - private void updateSuggestedFromLatest() { + private void updateSuggestedFromLatest(@Nullable String packageName) { Utils.debugLog(TAG, "Calculating suggested versions for all apps which don't specify an upstream version code."); final String apk = getApkTableName(); final String app = getTableName(); final String installed = InstalledAppTable.NAME; + final String restrictToApps; + final String[] args; + + if (packageName == null) { + restrictToApps = " COALESCE(" + Cols.UPSTREAM_VERSION_CODE + ", 0) = 0 OR " + Cols.SUGGESTED_VERSION_CODE + " IS NULL "; + args = null; + } else { + // Don't update an app with an upstream version code, because that would have been updated + // by updateSuggestedFromUpdate(packageName). + restrictToApps = " COALESCE(" + Cols.UPSTREAM_VERSION_CODE + ", 0) = 0 AND " + app + "." + Cols.PACKAGE_ID + " = (" + getPackageIdFromPackageNameQuery() + ") "; + args = new String[]{packageName}; + } + String updateSql = "UPDATE " + app + " SET " + Cols.SUGGESTED_VERSION_CODE + " = ( " + " SELECT MAX( " + apk + "." + ApkTable.Cols.VERSION_CODE + " ) " + @@ -1033,9 +1071,9 @@ public class AppProvider extends FDroidProvider { app + "." + Cols.PACKAGE_ID + " = appForThisApk." + Cols.PACKAGE_ID + " AND " + apk + "." + ApkTable.Cols.SIGNATURE + " = COALESCE(" + installed + "." + InstalledAppTable.Cols.SIGNATURE + ", " + apk + "." + ApkTable.Cols.SIGNATURE + ") AND " + " ( " + app + "." + Cols.IS_COMPATIBLE + " = 0 OR " + apk + "." + ApkTable.Cols.IS_COMPATIBLE + " = 1 ) ) " + - " WHERE COALESCE(" + Cols.UPSTREAM_VERSION_CODE + ", 0) = 0 OR " + Cols.SUGGESTED_VERSION_CODE + " IS NULL "; + " WHERE " + restrictToApps; - LoggingQuery.execSQL(db(), updateSql); + LoggingQuery.execSQL(db(), updateSql, args); } private void updateIconUrls() { diff --git a/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProvider.java b/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProvider.java index ec0396d65..3ecc7fa05 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProvider.java @@ -221,10 +221,17 @@ public class InstalledAppProvider extends FDroidProvider { throw new UnsupportedOperationException("Delete not supported for " + uri + "."); } + String packageName = uri.getLastPathSegment(); QuerySelection query = new QuerySelection(where, whereArgs); - query = query.add(queryAppSubQuery(uri.getLastPathSegment())); + query = query.add(queryAppSubQuery(packageName)); - return db().delete(getTableName(), query.getSelection(), query.getArgs()); + Utils.debugLog(TAG, "Deleting " + packageName); + int count = db().delete(getTableName(), query.getSelection(), query.getArgs()); + + Utils.debugLog(TAG, "Requesting the suggested apk get recalculated for " + packageName); + AppProvider.Helper.calcSuggestedApk(getContext(), packageName); + + return count; } @Override @@ -234,15 +241,23 @@ public class InstalledAppProvider extends FDroidProvider { throw new UnsupportedOperationException("Insert not supported for " + uri + "."); } - if (values.containsKey(Cols.Package.NAME)) { - String packageName = values.getAsString(Cols.Package.NAME); - long packageId = PackageProvider.Helper.ensureExists(getContext(), packageName); - values.remove(Cols.Package.NAME); - values.put(Cols.PACKAGE_ID, packageId); + if (!values.containsKey(Cols.Package.NAME)) { + throw new IllegalStateException("Package name not provided to InstalledAppProvider"); } + String packageName = values.getAsString(Cols.Package.NAME); + long packageId = PackageProvider.Helper.ensureExists(getContext(), packageName); + values.remove(Cols.Package.NAME); + values.put(Cols.PACKAGE_ID, packageId); + verifyVersionNameNotNull(values); + + Utils.debugLog(TAG, "Inserting/updating " + packageName); db().replaceOrThrow(getTableName(), null, values); + + Utils.debugLog(TAG, "Requesting the suggested apk get recalculated for " + packageName); + AppProvider.Helper.calcSuggestedApk(getContext(), packageName); + return getAppUri(values.getAsString(Cols.Package.NAME)); } diff --git a/app/src/main/java/org/fdroid/fdroid/data/LoggingQuery.java b/app/src/main/java/org/fdroid/fdroid/data/LoggingQuery.java index db6731307..bf257a0de 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/LoggingQuery.java +++ b/app/src/main/java/org/fdroid/fdroid/data/LoggingQuery.java @@ -69,14 +69,21 @@ final class LoggingQuery { private void execSQLInternal() { if (BuildConfig.DEBUG) { long startTime = System.currentTimeMillis(); - db.execSQL(query); long queryDuration = System.currentTimeMillis() - startTime; - + executeSQLInternal(); if (queryDuration >= SLOW_QUERY_DURATION) { logSlowQuery(queryDuration); } } else { + executeSQLInternal(); + } + } + + private void executeSQLInternal() { + if (queryArgs == null || queryArgs.length == 0) { db.execSQL(query); + } else { + db.execSQL(query, queryArgs); } } @@ -131,7 +138,7 @@ final class LoggingQuery { return new LoggingQuery(db, query, queryBuilderArgs).rawQuery(); } - public static void execSQL(SQLiteDatabase db, String sql) { - new LoggingQuery(db, sql, null).execSQLInternal(); + public static void execSQL(SQLiteDatabase db, String sql, String[] queryArgs) { + new LoggingQuery(db, sql, queryArgs).execSQLInternal(); } } diff --git a/app/src/test/java/org/fdroid/fdroid/TestUtils.java b/app/src/test/java/org/fdroid/fdroid/TestUtils.java index 837f35db7..0b9d6d313 100644 --- a/app/src/test/java/org/fdroid/fdroid/TestUtils.java +++ b/app/src/test/java/org/fdroid/fdroid/TestUtils.java @@ -8,6 +8,7 @@ import android.content.ContextWrapper; import android.content.pm.ProviderInfo; import org.fdroid.fdroid.data.App; +import org.fdroid.fdroid.data.AppProvider; import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.RepoProvider; import org.fdroid.fdroid.data.RepoProviderTest; @@ -157,4 +158,15 @@ public class TestUtils { } }; } + + /** + * Normally apps/apks are only added to the database in response to a repo update. + * At the end of a repo update, the {@link AppProvider} updates the suggested apks and + * recalculates the preferred metadata for each app. Because we are adding apps/apks + * directly to the database, we need to simulate this update after inserting stuff. + */ + public static void updateDbAfterInserting(Context context) { + AppProvider.Helper.calcSuggestedApks(context); + AppProvider.Helper.recalculatePreferredMetadata(context); + } } diff --git a/app/src/test/java/org/fdroid/fdroid/data/PreferredSignatureTest.java b/app/src/test/java/org/fdroid/fdroid/data/PreferredSignatureTest.java index 20c2d55e3..d86510ef1 100644 --- a/app/src/test/java/org/fdroid/fdroid/data/PreferredSignatureTest.java +++ b/app/src/test/java/org/fdroid/fdroid/data/PreferredSignatureTest.java @@ -25,6 +25,14 @@ public class PreferredSignatureTest extends FDroidProviderTest { public void setup() { TestUtils.registerContentProvider(AppProvider.getAuthority(), AppProvider.class); Preferences.setup(context); + + // This is what the FDroidApp does when this preference is changed. Need to also do this under testing. + Preferences.get().registerUnstableUpdatesChangeListener(new Preferences.ChangeListener() { + @Override + public void onPreferenceChange() { + AppProvider.Helper.calcSuggestedApks(context); + } + }); } @After @@ -46,6 +54,8 @@ public class PreferredSignatureTest extends FDroidProviderTest { TestUtils.insertApk(context, app, 2100, TestUtils.UPSTREAM_SIG); // 2.0 TestUtils.insertApk(context, app, 3100, TestUtils.UPSTREAM_SIG); // 3.0 + TestUtils.updateDbAfterInserting(context); + return app; } @@ -70,6 +80,8 @@ public class PreferredSignatureTest extends FDroidProviderTest { TestUtils.insertApk(context, app, 5002, TestUtils.THIRD_PARTY_SIG); // 5.0-rc2 TestUtils.insertApk(context, app, 5003, TestUtils.THIRD_PARTY_SIG); // 5.0-rc3 + TestUtils.updateDbAfterInserting(context); + return app; } @@ -84,6 +96,8 @@ public class PreferredSignatureTest extends FDroidProviderTest { TestUtils.insertApk(context, app, 3100, TestUtils.UPSTREAM_SIG); TestUtils.insertApk(context, app, 4100, TestUtils.UPSTREAM_SIG); + TestUtils.updateDbAfterInserting(context); + return app; } @@ -265,9 +279,6 @@ public class PreferredSignatureTest extends FDroidProviderTest { } private void assertSuggested(Context context, int suggestedVersion, String suggestedSig) { - AppProvider.Helper.calcSuggestedApks(context); - AppProvider.Helper.recalculatePreferredMetadata(context); - App suggestedApp = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(), PACKAGE_NAME); assertEquals("Suggested version on App", suggestedVersion, suggestedApp.suggestedVersionCode); diff --git a/app/src/test/java/org/fdroid/fdroid/data/SuggestedVersionTest.java b/app/src/test/java/org/fdroid/fdroid/data/SuggestedVersionTest.java index 010e2b638..447bd3301 100644 --- a/app/src/test/java/org/fdroid/fdroid/data/SuggestedVersionTest.java +++ b/app/src/test/java/org/fdroid/fdroid/data/SuggestedVersionTest.java @@ -26,6 +26,14 @@ public class SuggestedVersionTest extends FDroidProviderTest { public void setup() { TestUtils.registerContentProvider(AppProvider.getAuthority(), AppProvider.class); Preferences.setup(context); + + // This is what the FDroidApp does when this preference is changed. Need to also do this under testing. + Preferences.get().registerUnstableUpdatesChangeListener(new Preferences.ChangeListener() { + @Override + public void onPreferenceChange() { + AppProvider.Helper.calcSuggestedApks(context); + } + }); } @After @@ -40,6 +48,7 @@ public class SuggestedVersionTest extends FDroidProviderTest { TestUtils.insertApk(context, singleApp, 1, TestUtils.FDROID_SIG); TestUtils.insertApk(context, singleApp, 2, TestUtils.FDROID_SIG); TestUtils.insertApk(context, singleApp, 3, TestUtils.FDROID_SIG); + TestUtils.updateDbAfterInserting(context); assertSuggested("single.app", 2); // By enabling unstable updates, the "upstreamVersionCode" should get ignored, and we should @@ -59,6 +68,7 @@ public class SuggestedVersionTest extends FDroidProviderTest { TestUtils.insertApk(context, singleApp, 3, TestUtils.FDROID_SIG); TestUtils.insertApk(context, singleApp, 4, TestUtils.UPSTREAM_SIG); TestUtils.insertApk(context, singleApp, 5, TestUtils.UPSTREAM_SIG); + TestUtils.updateDbAfterInserting(context); // Given we aren't installed yet, we don't care which signature. // Just get as close to upstreamVersionCode as possible. @@ -72,6 +82,7 @@ public class SuggestedVersionTest extends FDroidProviderTest { // This adds the "upstreamVersionCode" version of the app, but signed by f-droid. TestUtils.insertApk(context, singleApp, 4, TestUtils.FDROID_SIG); TestUtils.insertApk(context, singleApp, 5, TestUtils.FDROID_SIG); + TestUtils.updateDbAfterInserting(context); assertSuggested("single.app", 4, TestUtils.FDROID_SIG, 1); // Version 5 from F-Droid is not the "upstreamVersionCode", but with beta updates it should @@ -99,6 +110,7 @@ public class SuggestedVersionTest extends FDroidProviderTest { TestUtils.insertApk(context, thirdPartyApp, 4, TestUtils.THIRD_PARTY_SIG); TestUtils.insertApk(context, thirdPartyApp, 5, TestUtils.THIRD_PARTY_SIG); TestUtils.insertApk(context, thirdPartyApp, 6, TestUtils.THIRD_PARTY_SIG); + TestUtils.updateDbAfterInserting(context); // Given we aren't installed yet, we don't care which signature or even which repo. // Just get as close to upstreamVersionCode as possible. @@ -112,6 +124,7 @@ public class SuggestedVersionTest extends FDroidProviderTest { // This adds the "upstreamVersionCode" version of the app, but signed by f-droid. TestUtils.insertApk(context, mainApp, 4, TestUtils.FDROID_SIG); TestUtils.insertApk(context, mainApp, 5, TestUtils.FDROID_SIG); + TestUtils.updateDbAfterInserting(context); assertSuggested("single.app", 4, TestUtils.FDROID_SIG, 1); // Uninstalling the F-Droid build and installing v3 of the third party means we can now go @@ -146,6 +159,7 @@ public class SuggestedVersionTest extends FDroidProviderTest { TestUtils.insertApk(context, mainApp, 5, TestUtils.UPSTREAM_SIG); TestUtils.insertApk(context, mainApp, 6, TestUtils.UPSTREAM_SIG); TestUtils.insertApk(context, mainApp, 7, TestUtils.UPSTREAM_SIG); + TestUtils.updateDbAfterInserting(context); // If the user was to manually install the app, they should be suggested version 7 from upstream... assertSuggested("single.app", 7); @@ -180,9 +194,6 @@ public class SuggestedVersionTest extends FDroidProviderTest { * apk is not checked. */ public void assertSuggested(String packageName, int suggestedVersion, String installedSig, int installedVersion) { - AppProvider.Helper.calcSuggestedApks(context); - AppProvider.Helper.recalculatePreferredMetadata(context); - App suggestedApp = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(), packageName); assertEquals("Suggested version on App", suggestedVersion, suggestedApp.suggestedVersionCode); assertEquals("Installed signature on App", installedSig, suggestedApp.installedSig); From 9acc5a231024680a8c3afc3b272bb3b217bad79b Mon Sep 17 00:00:00 2001 From: Peter Serwylo <peter@serwylo.com> Date: Mon, 3 Jul 2017 13:52:33 +1000 Subject: [PATCH 7/7] Make pre-multi-sig tests work again. Before mult-signature support, the process of marking an app as installed in the `InstalledAppProvider` didn't have any side effects beyond its own table. Now, it is also responsible for calculating the `suggestedVersionCode` of the associated app as well. This means old tests around suggested versions no longer work. This is because they would insert an App, and set the `Cols.SUGGESTED_VERSION_CODE` using a `ContentValues`. This was then overwritten by the `InstalledAppProvider` asking for the real calculation for suggested versions. That is - it would check for relevant apks and figure out which was best. To make the old tests correct, they need: * To be able to "install" apps with the correct signature. * To insert the relevant apks into the database, not just depend on the presence of an `app`. --- .../java/org/fdroid/fdroid/TestUtils.java | 6 ++++- .../fdroid/fdroid/data/AppProviderTest.java | 25 ++++++++++++++++--- .../fdroid/data/InstalledAppProviderTest.java | 8 ++++++ .../fdroid/data/InstalledAppTestUtils.java | 6 ++--- .../updater/ProperMultiRepoUpdaterTest.java | 3 ++- 5 files changed, 39 insertions(+), 9 deletions(-) diff --git a/app/src/test/java/org/fdroid/fdroid/TestUtils.java b/app/src/test/java/org/fdroid/fdroid/TestUtils.java index 0b9d6d313..d387c7355 100644 --- a/app/src/test/java/org/fdroid/fdroid/TestUtils.java +++ b/app/src/test/java/org/fdroid/fdroid/TestUtils.java @@ -35,7 +35,11 @@ public class TestUtils { @SuppressWarnings("unused") private static final String TAG = "TestUtils"; // NOPMD - public static final String FDROID_CERT = "308202ed308201d5a003020102020426ffa009300d06092a864886f70d01010b05003027310b300906035504061302444531183016060355040a130f4e4f47415050532050726f6a656374301e170d3132313030363132303533325a170d3337303933303132303533325a3027310b300906035504061302444531183016060355040a130f4e4f47415050532050726f6a65637430820122300d06092a864886f70d01010105000382010f003082010a02820101009a8d2a5336b0eaaad89ce447828c7753b157459b79e3215dc962ca48f58c2cd7650df67d2dd7bda0880c682791f32b35c504e43e77b43c3e4e541f86e35a8293a54fb46e6b16af54d3a4eda458f1a7c8bc1b7479861ca7043337180e40079d9cdccb7e051ada9b6c88c9ec635541e2ebf0842521c3024c826f6fd6db6fd117c74e859d5af4db04448965ab5469b71ce719939a06ef30580f50febf96c474a7d265bb63f86a822ff7b643de6b76e966a18553c2858416cf3309dd24278374bdd82b4404ef6f7f122cec93859351fc6e5ea947e3ceb9d67374fe970e593e5cd05c905e1d24f5a5484f4aadef766e498adf64f7cf04bddd602ae8137b6eea40722d0203010001a321301f301d0603551d0e04160414110b7aa9ebc840b20399f69a431f4dba6ac42a64300d06092a864886f70d01010b0500038201010007c32ad893349cf86952fb5a49cfdc9b13f5e3c800aece77b2e7e0e9c83e34052f140f357ec7e6f4b432dc1ed542218a14835acd2df2deea7efd3fd5e8f1c34e1fb39ec6a427c6e6f4178b609b369040ac1f8844b789f3694dc640de06e44b247afed11637173f36f5886170fafd74954049858c6096308fc93c1bc4dd5685fa7a1f982a422f2a3b36baa8c9500474cf2af91c39cbec1bc898d10194d368aa5e91f1137ec115087c31962d8f76cd120d28c249cf76f4c70f5baa08c70a7234ce4123be080cee789477401965cfe537b924ef36747e8caca62dfefdd1a6288dcb1c4fd2aaa6131a7ad254e9742022cfd597d2ca5c660ce9e41ff537e5a4041e37"; // NOCHECKSTYLE LineLength + /** + * This is the F-Droid signature used to sign the AdAway binaries used for the multiRepo.*.jar + * repos used by some tests. + */ + public static final String FDROID_CERT = "3082033c30820224a00302010202044e9c4ba6300d06092a864886f70d01010505003060310b300906035504061302554b310c300a060355040813034f5247310c300a060355040713034f524731133011060355040a130a6664726f69642e6f7267310f300d060355040b13064644726f6964310f300d060355040313064644726f6964301e170d3131313031373135333731305a170d3339303330343135333731305a3060310b300906035504061302554b310c300a060355040813034f5247310c300a060355040713034f524731133011060355040a130a6664726f69642e6f7267310f300d060355040b13064644726f6964310f300d060355040313064644726f696430820122300d06092a864886f70d01010105000382010f003082010a0282010100981b0aac96f1c66be3c21e773327ee8c4d3b18c75c548243f4cfedbe8ef0d3c6cc1b3b7b094ddd39cdf71d034ef2cd2d1e7bdca458801b04a531cbe7106a3575151375cb32177b017f81cc508f981a1809d0a417c6f3d59ddfa876c3d91874b1d59e08eaf757da13fb82f7e6f7340abc56f0ab672f02e957d446585931388b1affb6f43a16efc7f060df9c8da17c86899b19495114cc5939decd521e172b48e68c6ec03bc58776acd6a52fd61fd839d2a404df25ae79c2ccec2d9a07c9a1751c341e5e9b706b8e713bec2149e16f5ca15a1d6fe67d52ebb210995ee03d9416118fa9434f65ffe6d43dddfe3e2b0c54b94ea8e5a1031ed41856cd369da41dc6790203010001300d06092a864886f70d0101050500038201010080951aa68b5a2c7ac464b66078afd4826df96e2c10b612a441036e43aa923bfa55f26c61b5d94c2132877a3801c2394328f70b322f6308dbea6ed4f0f4897d73d13af9498277f60685239acd8922275544334d295b07245ef0ec924e1c35e8004d8d268d97c957078149cc5635f8977ce432a56278a03664a45a6be51319b0b5f3e27b2372ae859215e3f3d0f5c8b86d1a42f742abe4d224870d419600966e46d83ce41df04e315353f334378f0f994732a6c05d351b1bea66efc62471762d0f752d379966e8293fc5fe4150665427b0f3fb3a1b64c3b75128abadc02c3efa44c06e2d22ba8f1c3f4b782ac2da0d56307173093fde31215d26ab05714a12d696"; // NOCHECKSTYLE LineLength public static final String UPSTREAM_CERT = "308204e1308202c9a0030201020204483450fa300d06092a864886f70d01010b050030213110300e060355040b1307462d44726f6964310d300b06035504031304736f7661301e170d3136303832333133333131365a170d3434303130393133333131365a30213110300e060355040b1307462d44726f6964310d300b06035504031304736f766130820222300d06092a864886f70d01010105000382020f003082020a0282020100dfdcd120f3ab224999dddf4ea33ea588d295e4d7130bef48c143e9d76e5c0e0e9e5d45e64208e35feebc79a83f08939dd6a343b7d1e2179930a105a1249ccd36d88ff3feffc6e4dc53dae0163a7876dd45ecc1ddb0adf5099aa56c1a84b52affcd45d0711ffa4de864f35ac0333ebe61ea8673eeda35a88f6af678cc4d0f80b089338ac8f2a8279a64195c611d19445cab3fd1a020afed9bd739bb95142fb2c00a8f847db5ef3325c814f8eb741bacf86ed3907bfe6e4564d2de5895df0c263824e0b75407589bae2d3a4666c13b92102d8781a8ee9bb4a5a1a78c4a9c21efdaf5584da42e84418b28f5a81d0456a3dc5b420991801e6b21e38c99bbe018a5b2d690894a114bc860d35601416aa4dc52216aff8a288d4775cddf8b72d45fd2f87303a8e9c0d67e442530be28eaf139894337266e0b33d57f949256ab32083bcc545bc18a83c9ab8247c12aea037e2b68dee31c734cb1f04f241d3b94caa3a2b258ffaf8e6eae9fbbe029a934dc0a0859c5f120334812693a1c09352340a39f2a678dbc1afa2a978bfee43afefcb7e224a58af2f3d647e5745db59061236b8af6fcfd93b3602f9e456978534f3a7851e800071bf56da80401c81d91c45f82568373af0576b1cc5eef9b85654124b6319770be3cdba3fbebe3715e8918fb6c8966624f3d0e815effac3d2ee06dd34ab9c693218b2c7c06ba99d6b74d4f17b8c3cb0203010001a321301f301d0603551d0e04160414d62bee9f3798509546acc62eb1de14b08b954d4f300d06092a864886f70d01010b05000382020100743f7c5692085895f9d1fffad390fb4202c15f123ed094df259185960fd6dadf66cb19851070f180297bba4e6996a4434616573b375cfee94fee73a4505a7ec29136b7e6c22e6436290e3686fe4379d4e3140ec6a08e70cfd3ed5b634a5eb5136efaaabf5f38e0432d3d79568a556970b8cfba2972f5d23a3856d8a981b9e9bbbbb88f35e708bde9cbc5f681cbd974085b9da28911296fe2579fa64bbe9fa0b93475a7a8db051080b0c5fade0d1c018e7858cd4cbe95145b0620e2f632cbe0f8af9cbf22e2fdaa72245ae31b0877b07181cc69dd2df74454251d8de58d25e76354abe7eb690f22e59b08795a8f2c98c578e0599503d9085927634072c82c9f82abd50fd12b8fd1a9d1954eb5cc0b4cfb5796b5aaec0356643b4a65a368442d92ef94edd3ac6a2b7fe3571b8cf9f462729228aab023ef9183f73792f5379633ccac51079177d604c6bc1873ada6f07d8da6d68c897e88a5fa5d63fdb8df820f46090e0716e7562dd3c140ba279a65b996f60addb0abe29d4bf2f5abe89480771d492307b926d91f02f341b2148502903c43d40f3c6c86a811d060711f0698b384acdcc0add44eb54e42962d3d041accc715afd49407715adc09350cb55e8d9281a3b0b6b5fcd91726eede9b7c8b13afdebb2c2b377629595f1096ba62fb14946dbac5f3c5f0b4e5b712e7acc7dcf6c46cdc5e6d6dfdeee55a0c92c2d70f080ac6"; // NOCHECKSTYLE LineLength public static final String THIRD_PARTY_CERT = "308204e1308202c9a0030201020204483450fa300d06092a864886f70d01010b050030213110300e060355040b130abcdeabcde012340123400b06035504031304736f7661301e170d3136303832333133333131365a170d3434303130393133333131365a30213110300e060355040b1307462d44726f6964310d300b06035504031304736f766130820222300d06092a864886f70d01010105000382020f003082020a0282020100dfdcd120f3ab224999dddf4ea33ea588d295e4d7130bef48c143e9d76e5c0e0e9e5d45e64208e35feebc79a83f08939dd6a343b7d1e2179930a105a1249ccd36d88ff3feffc6e4dc53dae0163a7876dd45ecc1ddb0adf5099aa56c1a84b52affcd45d0711ffa4de864f35ac0333ebe61ea8673eeda35a88f6af678cc4d0f80b089338ac8f2a8279a64195c611d19445cab3fd1a020afed9bd739bb95142fb2c00a8f847db5ef3325c814f8eb741bacf86ed3907bfe6e4564d2de5895df0c263824e0b75407589bae2d3a4666c13b92102d8781a8ee9bb4a5a1a78c4a9c21efdaf5584da42e84418b28f5a81d0456a3dc5b420991801e6b21e38c99bbe018a5b2d690894a114bc860d35601416aa4dc52216aff8a288d4775cddf8b72d45fd2f87303a8e9c0d67e442530be28eaf139894337266e0b33d57f949256ab32083bcc545bc18a83c9ab8247c12aea037e2b68dee31c734cb1f04f241d3b94caa3a2b258ffaf8e6eae9fbbe029a934dc0a0859c5f120334812693a1c09352340a39f2a678dbc1afa2a978bfee43afefcb7e224a58af2f3d647e5745db59061236b8af6fcfd93b3602f9e456978534f3a7851e800071bf56da80401c81d91c45f82568373af0576b1cc5eef9b85654124b6319770be3cdba3fbebe3715e8918fb6c8966624f3d0e815effac3d2ee06dd34ab9c693218b2c7c06ba99d6b74d4f17b8c3cb0203010001a321301f301d0603551d0e04160414d62bee9f3798509546acc62eb1de14b08b954d4f300d06092a864886f70d01010b05000382020100743f7c5692085895f9d1fffad390fb4202c15f123ed094df259185960fd6dadf66cb19851070f180297bba4e6996a4434616573b375cfee94fee73a4505a7ec29136b7e6c22e6436290e3686fe4379d4e3140ec6a08e70cfd3ed5b634a5eb5136efaaabf5f38e0432d3d79568a556970b8cfba2972f5d23a3856d8a981b9e9bbbbb88f35e708bde9cbc5f681cbd974085b9da28911296fe2579fa64bbe9fa0b93475a7a8db051080b0c5fade0d1c018e7858cd4cbe95145b0620e2f632cbe0f8af9cbf22e2fdaa72245ae31b0877b07181cc69dd2df74454251d8de58d25e76354abe7eb690f22e59b08795a8f2c98c578e0599503d9085927634072c82c9f82abd50fd12b8fd1a9d1954eb5cc0b4cfb5796b5aaec0356643b4a65a368442d92ef94edd3ac6a2b7fe3571b8cf9f462729228aab023ef9183f73792f5379633ccac51079177d604c6bc1873ada6f07d8da6d68c897e88a5fa5d63fdb8df820f46090e0716e7562dd3c140ba279a65b996f60addb0abe29d4bf2f5abe89480771d492307b926d91f02f341b2148502903c43d40f3c6c86a811d060711f0698b384acdcc0add44eb54e42962d3d041accc715afd49407715adc09350cb55e8d9281a3b0b6b5fcd91726eede9b7c8b13afdebb2c2b377629595f1096ba62fb14946dbac5f3c5f0b4e5b712e7acc7dcf6c46cdc5e6d6dfdeee55a0c92c2d70f080ac6"; // NOCHECKSTYLE LineLength diff --git a/app/src/test/java/org/fdroid/fdroid/data/AppProviderTest.java b/app/src/test/java/org/fdroid/fdroid/data/AppProviderTest.java index b9402e83f..9152ded6a 100644 --- a/app/src/test/java/org/fdroid/fdroid/data/AppProviderTest.java +++ b/app/src/test/java/org/fdroid/fdroid/data/AppProviderTest.java @@ -8,8 +8,10 @@ import android.database.Cursor; import android.net.Uri; import org.fdroid.fdroid.BuildConfig; +import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.TestUtils; import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -22,6 +24,7 @@ import java.util.List; import static org.fdroid.fdroid.Assert.assertContainsOnly; import static org.fdroid.fdroid.Assert.assertResultCount; +import static org.fdroid.fdroid.Assert.insertApk; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -38,6 +41,12 @@ public class AppProviderTest extends FDroidProviderTest { @Before public void setup() { TestUtils.registerContentProvider(AppProvider.getAuthority(), AppProvider.class); + Preferences.setup(context); + } + + @After + public void tearDown() { + Preferences.clearSingletonForTesting(); } /** @@ -88,12 +97,20 @@ public class AppProviderTest extends FDroidProviderTest { private void insertAndInstallApp( String packageName, int installedVercode, int suggestedVercode, boolean ignoreAll, int ignoreVercode) { - ContentValues values = new ContentValues(3); - values.put(Cols.SUGGESTED_VERSION_CODE, suggestedVercode); - App app = insertApp(contentResolver, context, packageName, "App: " + packageName, values); + App app = insertApp(contentResolver, context, packageName, "App: " + packageName, new ContentValues()); AppPrefsProvider.Helper.update(context, app, new AppPrefs(ignoreVercode, ignoreAll)); - InstalledAppTestUtils.install(context, packageName, installedVercode, "v" + installedVercode); + ContentValues certValue = new ContentValues(1); + certValue.put(Schema.ApkTable.Cols.SIGNATURE, TestUtils.FDROID_SIG); + + // Make sure that the relevant apks are also in the DB, or else the `install` method below will + // not be able to correctly calculate the suggested version o the apk. + insertApk(context, packageName, installedVercode, certValue); + if (installedVercode != suggestedVercode) { + insertApk(context, packageName, suggestedVercode, certValue); + } + + InstalledAppTestUtils.install(context, packageName, installedVercode, "v" + installedVercode, TestUtils.FDROID_CERT); } @Test diff --git a/app/src/test/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java b/app/src/test/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java index cd5a3e38a..405967a8a 100644 --- a/app/src/test/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java +++ b/app/src/test/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java @@ -5,8 +5,10 @@ import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import org.fdroid.fdroid.BuildConfig; +import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.TestUtils; import org.fdroid.fdroid.data.Schema.InstalledAppTable.Cols; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -29,6 +31,12 @@ public class InstalledAppProviderTest extends FDroidProviderTest { @Before public void setup() { TestUtils.registerContentProvider(InstalledAppProvider.getAuthority(), InstalledAppProvider.class); + Preferences.setup(context); + } + + @After + public void tearDown() { + Preferences.clearSingletonForTesting(); } @Test diff --git a/app/src/test/java/org/fdroid/fdroid/data/InstalledAppTestUtils.java b/app/src/test/java/org/fdroid/fdroid/data/InstalledAppTestUtils.java index 9a9eec115..f4b599ae4 100644 --- a/app/src/test/java/org/fdroid/fdroid/data/InstalledAppTestUtils.java +++ b/app/src/test/java/org/fdroid/fdroid/data/InstalledAppTestUtils.java @@ -21,15 +21,15 @@ public class InstalledAppTestUtils { public static void install(Context context, String packageName, int versionCode, String versionName, - @Nullable String signature) { + @Nullable String signingCert) { PackageInfo info = new PackageInfo(); info.packageName = packageName; info.versionCode = versionCode; info.versionName = versionName; info.applicationInfo = new ApplicationInfo(); info.applicationInfo.publicSourceDir = "/tmp/mock-location"; - if (signature != null) { - info.signatures = new Signature[]{new Signature(signature)}; + if (signingCert != null) { + info.signatures = new Signature[]{new Signature(signingCert)}; } String hashType = "sha256"; String hash = "00112233445566778899aabbccddeeff"; diff --git a/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java b/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java index 7e776adf6..d887fdb7b 100644 --- a/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java +++ b/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java @@ -7,6 +7,7 @@ import android.util.Log; import org.fdroid.fdroid.BuildConfig; import org.fdroid.fdroid.RepoUpdater; +import org.fdroid.fdroid.TestUtils; import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.data.ApkProvider; import org.fdroid.fdroid.data.App; @@ -246,7 +247,7 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest { } private void assertCanUpdate(String packageName, int installedVersion, int expectedUpdateVersion) { - InstalledAppTestUtils.install(context, packageName, installedVersion, "v" + installedVersion); + InstalledAppTestUtils.install(context, packageName, installedVersion, "v" + installedVersion, TestUtils.FDROID_CERT); List<App> appsToUpdate = AppProvider.Helper.findCanUpdate(context, AppMetadataTable.Cols.ALL); assertEquals(1, appsToUpdate.size()); assertEquals(installedVersion, appsToUpdate.get(0).installedVersionCode);