diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index fb882c572..ff9fa3da2 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -8,40 +8,40 @@ dependencies { // NOTE: libraries are pinned to a specific build, see below // from local Android SDK - compile 'com.android.support:support-v4:24.1.1' - compile 'com.android.support:appcompat-v7:24.1.1' - compile 'com.android.support:design:24.1.1' - compile 'com.android.support:recyclerview-v7:24.1.1' - compile 'com.android.support:cardview-v7:24.1.1' - compile 'com.android.support:support-annotations:24.1.1' + compile 'com.android.support:support-v4:25.0.0' + compile 'com.android.support:appcompat-v7:25.0.0' + compile 'com.android.support:design:25.0.0' + compile 'com.android.support:recyclerview-v7:25.0.0' + compile 'com.android.support:cardview-v7:25.0.0' + compile 'com.android.support:support-annotations:25.0.0' // JCenter etc. - compile 'com.journeyapps:zxing-android-embedded:3.2.0@aar' - compile 'com.google.zxing:core:3.2.1' - compile 'com.jpardogo.materialtabstrip:library:1.1.0' + compile 'com.journeyapps:zxing-android-embedded:3.4.0@aar' + compile 'com.google.zxing:core:3.3.0' + compile 'com.jpardogo.materialtabstrip:library:1.1.1' compile 'com.getbase:floatingactionbutton:1.10.1' compile 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.0' compile 'org.ocpsoft.prettytime:prettytime:4.0.1.Final' - compile 'com.splitwise:tokenautocomplete:2.0.7@aar' + compile 'com.splitwise:tokenautocomplete:2.0.8@aar' compile 'se.emilsjolander:stickylistheaders:2.7.0' - compile 'org.sufficientlysecure:html-textview:1.8' + compile 'org.sufficientlysecure:html-textview:2.0' compile 'org.sufficientlysecure:donations:2.4' compile 'com.nispok:snackbar:2.11.0' compile 'com.squareup.okhttp3:okhttp:3.2.0' compile 'com.squareup.okhttp3:okhttp-urlconnection:3.2.0' - compile 'org.apache.james:apache-mime4j-core:0.7.2' - compile 'org.apache.james:apache-mime4j-dom:0.7.2' + compile 'org.apache.james:apache-mime4j-core:0.8.0' + compile 'org.apache.james:apache-mime4j-dom:0.8.0' compile 'org.thoughtcrime.ssl.pinning:AndroidPinning:1.0.0' compile 'com.cocosw:bottomsheet:1.3.0@aar' // Material Drawer - compile 'com.mikepenz:materialdrawer:5.2.2@aar' - compile 'com.mikepenz:fastadapter:1.4.4' - compile 'com.mikepenz:materialize:0.8.8' - compile 'com.mikepenz:iconics-core:2.5.11@aar' - compile 'com.mikepenz:google-material-typeface:2.2.0.1.original@aar' - compile 'com.mikepenz:fontawesome-typeface:4.6.0.1@aar' - compile 'com.mikepenz:community-material-typeface:1.5.54.1@aar' + compile 'com.mikepenz:materialdrawer:5.6.0@aar' + compile 'com.mikepenz:fastadapter:1.8.2' + compile 'com.mikepenz:materialize:1.0.0' + compile 'com.mikepenz:iconics-core:2.8.1@aar' + compile 'com.mikepenz:google-material-typeface:2.2.0.3.original@aar' + compile 'com.mikepenz:fontawesome-typeface:4.6.0.3@aar' + compile 'com.mikepenz:community-material-typeface:1.5.54.2@aar' // Nordpol compile 'com.fidesmo:nordpol-android:0.1.18' @@ -69,9 +69,9 @@ dependencies { // UI testing with Espresso // Force usage of support libs in the test app, since they are internally used by the runner module. // https://github.com/googlesamples/android-testing/blob/master/ui/espresso/BasicSample/app/build.gradle#L28 - androidTestCompile 'com.android.support:support-annotations:24.1.1' - androidTestCompile 'com.android.support:appcompat-v7:24.1.1' - androidTestCompile 'com.android.support:design:24.1.1' + androidTestCompile 'com.android.support:support-annotations:25.0.0' + androidTestCompile 'com.android.support:appcompat-v7:25.0.0' + androidTestCompile 'com.android.support:design:25.0.0' androidTestCompile 'com.android.support.test:runner:0.5' androidTestCompile 'com.android.support.test:rules:0.5' androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2' @@ -88,47 +88,56 @@ dependencies { // Comment out the libs referenced as git submodules! dependencyVerification { verify = [ - 'com.android.support:support-v4:246c99385a84fe179d7b833c9ddaf2576f217b0abba5e74b5353cc78756f5880', - 'com.android.support:appcompat-v7:bf8db89d678286043778990fc967346e94321cc8d8bad99e9b0db20588509156', - 'com.android.support:design:391d3d1fd274c3150e949653b9863af61e80953bbb90f3cbd40e9d2f1bd548d9', - 'com.android.support:recyclerview-v7:e89e0ba2b73bb4002965ff42f93cfe3e7da05162716b15218e31f7dd140fa40e', - 'com.android.support:cardview-v7:eb74ab41045d182fcf783c7479c5b35fb1f4d771ed307b2e7b84ab3a8fe10e96', - 'com.android.support:support-annotations:bac4e534657165b0c4c362c97db389dcb152e43273435d2ccaa939a82e03f42c', - 'com.journeyapps:zxing-android-embedded:afe4cd51d95ba0fd3a4bfe08c5a160bd32602aa174d511600ac824b6de4c79f1', - 'com.google.zxing:core:b4d82452e7a6bf6ec2698904b332431717ed8f9a850224f295aec89de80f2259', - 'com.jpardogo.materialtabstrip:library:24d19232b319f8c73e25793432357919a7ed972186f57a3b2c9093ea74ad8311', + 'com.android.support:support-v4:c25c657954152c4315584572f5008a6780b086169519f67acd20c2617b9da325', + 'com.android.support:appcompat-v7:bc0b13b1ba63ed226ae509044c880fdfe225cb86d21924ef19f2fb97a71c877e', + 'com.android.support:design:2fc13bacb1d293e3f35d9de509da6be6f5f0f50cf8c3796296fabbd63d76e9ab', + 'com.android.support:recyclerview-v7:fc6d6a9b802ed3a26789812f29167135cac7b4d956f4deda54fc1f317721da64', + 'com.android.support:cardview-v7:b9b214512995fd98490466bd98af08404866369e9526b450c0e2cf7b1e607bb5', + 'com.android.support:support-annotations:18766c4a12f18a0d96a383bc8c84d7a9c31ba2d1721622e0af814f8413ff06ec', + 'com.journeyapps:zxing-android-embedded:2422d83c2c09a7b645f516c8458ececba6a7da47b94e40778d876facf495c660', + 'com.google.zxing:core:bba7724e02a997cec38213af77133ee8e24b0d5cf5fa7ecbc16a4fa93f11ee0d', + 'com.jpardogo.materialtabstrip:library:4ee2f1211c302b45fb8c627cc5b240dc6b38b7aaaab1b8bffc81663e1b108013', 'com.getbase:floatingactionbutton:3edefa511aac4d90794c7b0496aca59cff2eee1e32679247b4f85acbeee05240', 'org.commonjava.googlecode.markdown4j:markdown4j:e952e825d29e1317d96f79f346bfb6786c7c5eef50bd26e54a80823704b62e13', 'org.ocpsoft.prettytime:prettytime:ef7098d973ae78b57d1a22dc37d3b8a771bf030301300e24055d676b6cdc5e75', - 'com.splitwise:tokenautocomplete:f56239588390f103b270b7c12361d99b06313a5a0410dc7f66e241ac4baf9baa', + 'com.splitwise:tokenautocomplete:f921f83ee26b5265f719b312c30452ef8e219557826c5ce5bf02e29647967939', 'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb', - 'org.sufficientlysecure:html-textview:206f484fe4178be6c831fe680de558764967e7b56496c4cc7f37f2979a477df6', + 'org.sufficientlysecure:html-textview:302c449167f9573313e5293ccab689010e028e4d09aee2ccc2682b3211227ce7', 'org.sufficientlysecure:donations:96f8197bab26dfe41900d824f10f8f1914519cd62eedb77bdac5b223eccdf0a6', 'com.nispok:snackbar:46b5eb9d630d329e13c2ce00ee9fb115ffb66c23c72cff32ee97eedd76824c6f', 'com.squareup.okhttp3:okhttp:a41cdb7b024c56436a21e38f00b4d12e3b7e01451ffe6c4f545acba805bba03b', 'com.squareup.okhttp3:okhttp-urlconnection:7d6598a6665c166e2d4b78956a96056b9be7de192b3c923ccf4695d08e580833', - 'org.apache.james:apache-mime4j-core:4d7434c68f94b81a253c12f28e6bbb4d6239c361d6086a46e22e594bb43ac660', - 'org.apache.james:apache-mime4j-dom:7e6b06ee164a1c21b7e477249ea0b74a18fddce44764e5764085f58dd8c34633', + 'org.apache.james:apache-mime4j-core:561987f604911e1870b2b4eabf0b0658d666c66cb1e65fba3e9e4bffe63acab9', + 'org.apache.james:apache-mime4j-dom:e18717fe6d36f32e5c5f7cbeea1a9bf04645fdabc84e7e8374d9da10fd52e78d', 'org.thoughtcrime.ssl.pinning:AndroidPinning:afa1d74e699257fa75cb109ff29bac50726ef269c6e306bdeffe8223cee06ef4', 'com.cocosw:bottomsheet:4af6112a7f4cad4e2b70e5fdf1edc39f51275523a0f53011a012837dc103e597', - 'com.mikepenz:materialdrawer:4169462fdde042e2bb53a7c2b4e2334d569d16b2020781ee05741b50e1a2967d', - 'com.mikepenz:fastadapter:1bfc00216d71dfdfe0d8e7a9d92bb97bfaa1794543930e34b1f79d5d7adbddf6', - 'com.mikepenz:materialize:575195b2fa5b2414fb14a59470ee21d8a8cd8355b651e0cf52e477e3ff1cd96c', - 'com.mikepenz:iconics-core:d57c6b0ecb33d9ed9708da62e07ad8993d80a16f374f7a2018be7837a60b7ed7', - 'com.mikepenz:google-material-typeface:47eabb0aadcc0f56530c3ca462b671a5cf5251101cef8f581aedf90ca511914a', - 'com.mikepenz:fontawesome-typeface:033cf3460d8074bd37a1fefc2ff4eac8f2e3db835ec78bf386d46710e4d0827c', - 'com.mikepenz:community-material-typeface:382e8446fc08fe03cb1e0f91ee329ffd514c113ad22f8389b88424ac71ed5fbb', + 'com.mikepenz:materialdrawer:8bba1428dcef5ad7c2decf49c612ad980b38e2f1031cbd66c152a8a104793929', + 'com.mikepenz:fastadapter:21d4ecb5c128bcda37b14e7998d799ed52cfc768b72cdf3d5578bb6775769ebd', + 'com.mikepenz:materialize:942ccf5e2aa1a46803aa884e8dc7bbaf2a9e8e9996a0cf92e3fe2f44a8592ba4', + 'com.mikepenz:iconics-core:478d7e245098f7c28b5b20a0e6b1e5cb108ef3eaf595af7190bc60f91063aa3d', + 'com.mikepenz:google-material-typeface:f27c629ba5d2a90ecfbd7f221ff98cd363e1ee6be06b099b82bae490766e14a5', + 'com.mikepenz:fontawesome-typeface:ee47b7fe97b90412f01f2fcdd78f65a4edb0ab00006f5ef59ed00516baca9309', + 'com.mikepenz:community-material-typeface:d6035d261c5eba880cd7fe5dcb8cc00b09bfe6d41063b881b759e9897dc7b7c9', 'com.fidesmo:nordpol-android:56f43fe2b1676817bcb4085926de14a08282ef6729c855c198d81aec62b20d65', -// 'OpenKeychain:openpgp-api-lib:2c145be0d124d37558f65ed962c47358b7e424dd4c00dc8818aad64598664c3d', -// 'OpenKeychain:openkeychain-api-lib:afff9f8410d8781fcd4023ca247d876ca124a6ee4f718afbc3740fc6078a255d', -// 'OpenKeychain.extern.bouncycastle:core:18876ec7629c427002ee00bb361b016b86159dcd9aac68951aec92f9ca41bb7a', -// 'OpenKeychain.extern.bouncycastle:pg:b6469f69d17ecb43c86c082fb1b5c01ae2f8e7a8ca5fa29e9f199c74dc021be1', -// 'OpenKeychain.extern.bouncycastle:prov:db7c1a72a86d918a25fc1ca8553762d1280274e8e2f02299f344b244bb527708', -// 'OpenKeychain.extern:minidns:8b324812d4b9ea9d639be43183f4292e5f257319fbb6c23faf2ee420f041d279', -// 'OpenKeychain:KeybaseLib:a992fb93c718fa34ab45850e34a294e413c9410e2b00243a43e6f303d85b6147', -// 'OpenKeychain:safeslinger-exchange:99d6ce7745edf5341495932bb26d0d1f8894b1edbfbddffc8514b2cf4149ad9b', - 'com.android.support:animated-vector-drawable:4fcd1fc36034a804200ef3e552b0f2f688a0a7a8a007de43201e40bfedda73b3', - 'com.android.support:support-vector-drawable:45b1f180b437a750429f6c1457181c167ba211c17fcb992f83cdbefef5eb1519', +// 'OpenKeychain:openpgp-api-lib:43d7e67e8ae19ae46e228393a7c4f5861da64db47da2fbb398817bde774669f7', +// 'OpenKeychain:openkeychain-api-lib:d2e66ce4828c47cb6081cfb8e7352681cc5d337404aec87e156cbc5a6e732587', +// 'OpenKeychain.extern.bouncycastle:core:20bd3036f93758c5ed937fc172b5e7e8d82d071e7b435bd9b6fa3869d5788095', +// 'OpenKeychain.extern.bouncycastle:pg:97b7318ee208b8fe552425aac9ffdcc8723032a78784ecc82c999ee09161e2e8', +// 'OpenKeychain.extern.bouncycastle:prov:76a8d07e88a557f91fad5c01f1f827085bde12ee924fa4d186a65d80e39d42a2', +// 'OpenKeychain.extern:minidns:8183e688b754b9e64ea498b735890a3ece9af1e240d949b804bf7b12a0d5b830', +// 'OpenKeychain:KeybaseLib:a870229a22779dfe433d6e48970822b0dfcd351c3cc885cfb94816d064c10fb0', +// 'OpenKeychain:safeslinger-exchange:2cbff71ec5d59c824dd244042539cfc5cfbf450ab813c95b9dc83fbb50401ba9', +// 'com.android.databinding:library:def2976cb30dd5abf9f3a35d70c70cfb5485af4fb4ae022f5b9a6e2f8cff6386', +// 'com.android.databinding:baseLibrary:47cb0d2d4d1aae4af3f860c31540493332a26278c016bbae90d22fdde3b0b83d', +// 'com.android.databinding:adapters:0dd06349dad760f3cb56769f8e9f46451634be6a8a1bfdb2e88a5ca10afcebd6', + 'com.android.support:support-compat:94648aef50a1938b541465d6d126769f20e77e247c7bca10b9b8cb61caa43398', + 'com.android.support:support-media-compat:8665ae69c74b2879bdce2bdea6d3b7296126487d0ea6fa2f9dd9ffdd5a5cee86', + 'com.android.support:support-core-utils:1808deca97864e78066d93d998f71bf71b0a774b6ea39c4e91fc1376d090728d', + 'com.android.support:support-core-ui:cdd0e8ec4d82e30f4c8ff2897997d59330992b670023fd2793a89a226ac960d6', + 'com.android.support:support-fragment:e47441b6a0e01b20cf744e3a8383ab60d922badcfde37c0b35fb078c84f2eae0', + 'com.android.support:support-vector-drawable:0758104bc324c1638705cd3cddf773639679bab5af28e3a63093655113d75d20', + 'com.android.support:animated-vector-drawable:9539f5dd8a919692cef064f72658034bf921a19ec14a4ff4abdfa3b478f3a3f9', + 'com.android.support:transition:331d4152f878442b7bf92c4320335ec7f707d1f6248da2fb3667be436dd508d1', 'com.squareup.okio:okio:114bdc1f47338a68bcbc95abf2f5cdc72beeec91812f2fcd7b521c1937876266', 'com.fidesmo:nordpol-core:3de58e850a00bba5b4d3a604d1399bcd89f695ea191ec0b03a57222e18062d15', ] @@ -142,7 +151,7 @@ android { defaultConfig { minSdkVersion 15 - targetSdkVersion 24 + targetSdkVersion 25 versionCode 42000 versionName "4.2 beta1" applicationId "org.sufficientlysecure.keychain" @@ -274,7 +283,6 @@ android { } dexOptions { - incremental = true preDexLibraries = true // dexInProcess requires much RAM, which is not available on all dev systems dexInProcess = false @@ -291,6 +299,10 @@ android { exclude 'META-INF/NOTICE' exclude '.readme' } + + dataBinding { + enabled = true + } } task jacocoTestReport(type:JacocoReport, dependsOn: "testFdroidDebugWithTestCoverageUnitTest") { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java index 18cbd4561..a1b2289f4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java @@ -17,6 +17,8 @@ package org.sufficientlysecure.keychain.keyimport; +import android.support.annotation.NonNull; + import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableProxy; @@ -26,8 +28,6 @@ import java.net.Proxy; import java.util.ArrayList; import java.util.Vector; -import android.support.annotation.NonNull; - /** * Search two or more types of server for online keys. */ @@ -38,8 +38,8 @@ public class CloudSearch { public static ArrayList search( @NonNull final String query, Preferences.CloudSearchPrefs cloudPrefs, @NonNull final ParcelableProxy proxy) throws Keyserver.CloudSearchFailureException { - final ArrayList servers = new ArrayList<>(); + final ArrayList servers = new ArrayList<>(); // it's a Vector for sync, multiple threads might report problems final Vector problems = new Vector<>(); @@ -52,46 +52,48 @@ public class CloudSearch { if (cloudPrefs.searchFacebook) { servers.add(new FacebookKeyserver()); } - final ImportKeysList results = new ImportKeysList(servers.size()); - ArrayList searchThreads = new ArrayList<>(); - for (final Keyserver keyserver : servers) { - Runnable r = new Runnable() { - @Override - public void run() { - try { - results.addAll(keyserver.search(query, proxy)); - } catch (Keyserver.CloudSearchFailureException e) { - problems.add(e); + int numberOfServers = servers.size(); + final ImportKeysList results = new ImportKeysList(numberOfServers); + + if (numberOfServers > 0) { + ArrayList searchThreads = new ArrayList<>(); + for (final Keyserver keyserver : servers) { + Runnable r = new Runnable() { + @Override + public void run() { + try { + results.addAll(keyserver.search(query, proxy)); + } catch (Keyserver.CloudSearchFailureException e) { + problems.add(e); + } + results.finishedAdding(); // notifies if all searchers done } - results.finishedAdding(); // notifies if all searchers done - } - }; - Thread searchThread = new Thread(r); - searchThreads.add(searchThread); - searchThread.start(); - } - - // wait for either all the searches to come back, or 10 seconds. If using proxy, wait 30 seconds. - synchronized (results) { - try { - if (proxy.getProxy() == Proxy.NO_PROXY) { - results.wait(30 * SECONDS); - } else { - results.wait(10 * SECONDS); - } - for (Thread thread : searchThreads) { - // kill threads that haven't returned yet - thread.interrupt(); - } - } catch (InterruptedException ignored) { + }; + Thread searchThread = new Thread(r); + searchThreads.add(searchThread); + searchThread.start(); } - } - if (results.outstandingSuppliers() > 0) { - String message = "Launched " + servers.size() + " cloud searchers, but " + - results.outstandingSuppliers() + "failed to complete."; - problems.add(new Keyserver.QueryFailedException(message)); + // wait for either all the searches to come back, or 10 seconds. If using proxy, wait 30 seconds. + synchronized (results) { + try { + results.wait((proxy.getProxy() == Proxy.NO_PROXY ? 30 : 10) * SECONDS); + for (Thread thread : searchThreads) { + // kill threads that haven't returned yet + thread.interrupt(); + } + } catch (InterruptedException ignored) { + } + } + + if (results.outstandingSuppliers() > 0) { + String message = "Launched " + servers.size() + " cloud searchers, but " + + results.outstandingSuppliers() + "failed to complete."; + problems.add(new Keyserver.QueryFailedException(message)); + } + } else { + problems.add(new Keyserver.QueryNoEnabledSourceException()); } if (!problems.isEmpty()) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/FacebookKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/FacebookKeyserver.java index 661b50424..6d0db66ba 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/FacebookKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/FacebookKeyserver.java @@ -23,11 +23,6 @@ import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; - -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; - import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; @@ -40,13 +35,16 @@ import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.TlsHelper; import java.io.IOException; - import java.net.Proxy; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + public class FacebookKeyserver extends Keyserver { private static final String FB_KEY_URL_FORMAT @@ -54,10 +52,6 @@ public class FacebookKeyserver extends Keyserver { private static final String FB_HOST = "facebook.com"; private static final String FB_HOST_WWW = "www." + FB_HOST; - public static final String FB_URL = "https://" + FB_HOST_WWW; - - public static final String ORIGIN = FB_URL; - public FacebookKeyserver() { } @@ -85,7 +79,7 @@ public class FacebookKeyserver extends Keyserver { @Override public String get(String fbUsername, ParcelableProxy proxy) throws QueryFailedException { - Log.d(Constants.TAG, "FacebookKeyserver get: " + fbUsername + " using Proxy: " + proxy); + Log.d(Constants.TAG, "FacebookKeyserver get: " + fbUsername + " using Proxy: " + proxy.getProxy()); String data = query(fbUsername, proxy); @@ -102,14 +96,21 @@ public class FacebookKeyserver extends Keyserver { private String query(String fbUsername, ParcelableProxy proxy) throws QueryFailedException { try { - String request = String.format(FB_KEY_URL_FORMAT, fbUsername); - Log.d(Constants.TAG, "fetching from Facebook with: " + request + " proxy: " + proxy); + URL url = new URL(String.format(FB_KEY_URL_FORMAT, fbUsername)); + Log.d(Constants.TAG, "fetching from Facebook with: " + url + " proxy: " + proxy.getProxy()); - URL url = new URL(request); + /* + * For some URLs such as https://www.facebook.com/adithya.abraham/publickey/download + * Facebook redirects to a mobile version (302) and then errors out (500). + * Using a desktop User-Agent solves that! + */ + Request request = new Request.Builder() + .url(url) + .header("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:49.0) Gecko/20100101 Firefox/49.0") + .build(); OkHttpClient client = OkHttpClientFactory.getClientPinnedIfAvailable(url, proxy.getProxy()); - - Response response = client.newCall(new Request.Builder().url(url).build()).execute(); + Response response = client.newCall(request).execute(); // contains body both in case of success or failure String responseBody = response.body().string(); @@ -118,7 +119,8 @@ public class FacebookKeyserver extends Keyserver { return responseBody; } else { // probably a 404 indicating that the key does not exist - throw new QueryFailedException("key for " + fbUsername + " not found on Facebook"); + throw new QueryFailedException("key for " + fbUsername + " not found on Facebook." + + "response:" + response); } } catch (IOException e) { @@ -128,7 +130,7 @@ public class FacebookKeyserver extends Keyserver { + (proxy.getProxy() == Proxy.NO_PROXY ? "" : " Using proxy " + proxy.getProxy())); } catch (TlsHelper.TlsHelperException e) { Log.e(Constants.TAG, "Exception in cert pinning", e); - throw new QueryFailedException("Exception in cert pinning. "); + throw new QueryFailedException("Exception in cert pinning."); } } @@ -148,7 +150,6 @@ public class FacebookKeyserver extends Keyserver { throws UnsupportedOperationException { ImportKeysListEntry entry = new ImportKeysListEntry(); entry.setSecretKey(false); // keys imported from Facebook must be public - entry.addOrigin(ORIGIN); // so we can query for the Facebook username directly, and to identify the source to // download the key from @@ -156,17 +157,11 @@ public class FacebookKeyserver extends Keyserver { UncachedPublicKey key = ring.getPublicKey(); - entry.setPrimaryUserId(key.getPrimaryUserIdWithFallback()); entry.setUserIds(key.getUnorderedUserIds()); - entry.updateMergedUserIds(); - entry.setPrimaryUserId(key.getPrimaryUserIdWithFallback()); entry.setKeyId(key.getKeyId()); - entry.setKeyIdHex(KeyFormattingUtils.convertKeyIdToHex(key.getKeyId())); - - entry.setFingerprintHex(KeyFormattingUtils.convertFingerprintToHex(key.getFingerprint())); - + entry.setFingerprint(key.getFingerprint()); try { if (key.isEC()) { // unsupported key format (ECDH or ECDSA) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysList.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysList.java index 75a219191..2e7872b82 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysList.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysList.java @@ -70,44 +70,25 @@ public class ImportKeysList extends ArrayList { modified = true; } - // keep track if this key result is from a HKP keyserver - boolean incomingFromHkpServer = true; - // we’re going to want to try to fetch the key from everywhere we found it, so remember - // all the origins - for (String origin : incoming.getOrigins()) { - existing.addOrigin(origin); + if (incoming.getKeyserver() != null) { + existing.setKeyserver(incoming.getKeyserver()); + // Mail addresses returned by HKP servers are preferred over keybase.io IDs + existing.setPrimaryUserId(incoming.getPrimaryUserId()); + modified = true; + } else if (incoming.getKeybaseName() != null) { // to work properly, Keybase-sourced/Facebook-sourced entries need to pass along the // identifying name/id - if (incoming.getKeybaseName() != null) { - existing.setKeybaseName(incoming.getKeybaseName()); - // one of the origins is not a HKP keyserver - incomingFromHkpServer = false; - } - if (incoming.getFbUsername() != null) { - existing.setFbUsername(incoming.getFbUsername()); - // one of the origins is not a HKP keyserver - incomingFromHkpServer = false; - } + existing.setKeybaseName(incoming.getKeybaseName()); + modified = true; + } else if (incoming.getFbUsername() != null) { + existing.setFbUsername(incoming.getFbUsername()); + modified = true; } - ArrayList incomingIDs = incoming.getUserIds(); - ArrayList existingIDs = existing.getUserIds(); - for (String incomingID : incomingIDs) { - if (!existingIDs.contains(incomingID)) { - // prepend HKP server results to the start of the list, - // so that the UI (for cloud key search, which is picking the first list item) - // shows the right main email address, as mail addresses returned by HKP servers - // are preferred over keybase.io IDs - if (incomingFromHkpServer) { - existingIDs.add(0, incomingID); - } else { - existingIDs.add(incomingID); - } - modified = true; - } - } - existing.updateMergedUserIds(); + if (existing.addUserIds(incoming.getUserIds())) + modified = true; + return modified; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java index c0cc9e2a3..89c3fcfb1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java @@ -21,108 +21,59 @@ import android.content.Context; import android.os.Parcel; import android.os.Parcelable; -import org.openintents.openpgp.util.OpenPgpUtils; -import org.sufficientlysecure.keychain.R; +import org.openintents.openpgp.util.OpenPgpUtils.UserId; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.UncachedPublicKey; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; +import java.util.List; +import java.util.Map; public class ImportKeysListEntry implements Serializable, Parcelable { private static final long serialVersionUID = -7797972103284992662L; + private ParcelableKeyRing mParcelableKeyRing; + private ArrayList mUserIds; private HashMap> mMergedUserIds; - private long mKeyId; + private ArrayList>> mSortedUserIds; + private String mKeyIdHex; + + private boolean mSecretKey; private boolean mRevoked; private boolean mExpired; - private Date mDate; // TODO: not displayed + private boolean mUpdated; + + private Date mDate; private String mFingerprintHex; private Integer mBitStrength; private String mCurveOid; private String mAlgorithm; - private boolean mSecretKey; - private String mPrimaryUserId; + + private UserId mPrimaryUserId; + private ParcelableHkpKeyserver mKeyserver; private String mKeybaseName; private String mFbUsername; + private String mQuery; - private ArrayList mOrigins; private Integer mHashCode = null; - private boolean mSelected; - - public int describeContents() { - return 0; + public ParcelableKeyRing getParcelableKeyRing() { + return mParcelableKeyRing; } - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(mPrimaryUserId); - dest.writeStringList(mUserIds); - dest.writeSerializable(mMergedUserIds); - dest.writeLong(mKeyId); - dest.writeByte((byte) (mRevoked ? 1 : 0)); - dest.writeByte((byte) (mExpired ? 1 : 0)); - dest.writeInt(mDate == null ? 0 : 1); - if (mDate != null) { - dest.writeLong(mDate.getTime()); - } - dest.writeString(mFingerprintHex); - dest.writeString(mKeyIdHex); - dest.writeInt(mBitStrength == null ? 0 : 1); - if (mBitStrength != null) { - dest.writeInt(mBitStrength); - } - dest.writeString(mAlgorithm); - dest.writeByte((byte) (mSecretKey ? 1 : 0)); - dest.writeByte((byte) (mSelected ? 1 : 0)); - dest.writeString(mKeybaseName); - dest.writeString(mFbUsername); - dest.writeStringList(mOrigins); - } - - public static final Creator CREATOR = new Creator() { - public ImportKeysListEntry createFromParcel(final Parcel source) { - ImportKeysListEntry vr = new ImportKeysListEntry(); - vr.mPrimaryUserId = source.readString(); - vr.mUserIds = new ArrayList<>(); - source.readStringList(vr.mUserIds); - vr.mMergedUserIds = (HashMap>) source.readSerializable(); - vr.mKeyId = source.readLong(); - vr.mRevoked = source.readByte() == 1; - vr.mExpired = source.readByte() == 1; - vr.mDate = source.readInt() != 0 ? new Date(source.readLong()) : null; - vr.mFingerprintHex = source.readString(); - vr.mKeyIdHex = source.readString(); - vr.mBitStrength = source.readInt() != 0 ? source.readInt() : null; - vr.mAlgorithm = source.readString(); - vr.mSecretKey = source.readByte() == 1; - vr.mSelected = source.readByte() == 1; - vr.mKeybaseName = source.readString(); - vr.mFbUsername = source.readString(); - vr.mOrigins = new ArrayList<>(); - source.readStringList(vr.mOrigins); - - return vr; - } - - public ImportKeysListEntry[] newArray(final int size) { - return new ImportKeysListEntry[size]; - } - }; - - public int hashCode() { - if (mHashCode != null) { - return mHashCode; - } - return super.hashCode(); + public void setParcelableKeyRing(ParcelableKeyRing parcelableKeyRing) { + this.mParcelableKeyRing = parcelableKeyRing; } public boolean hasSameKeyAs(ImportKeysListEntry other) { @@ -136,12 +87,12 @@ public class ImportKeysListEntry implements Serializable, Parcelable { return mKeyIdHex; } - public boolean isSelected() { - return mSelected; + public void setKeyIdHex(String keyIdHex) { + mKeyIdHex = keyIdHex; } - public void setSelected(boolean selected) { - mSelected = selected; + public void setKeyId(long keyId) { + mKeyIdHex = KeyFormattingUtils.convertKeyIdToHex(keyId); } public boolean isExpired() { @@ -152,18 +103,6 @@ public class ImportKeysListEntry implements Serializable, Parcelable { mExpired = expired; } - public long getKeyId() { - return mKeyId; - } - - public void setKeyId(long keyId) { - mKeyId = keyId; - } - - public void setKeyIdHex(String keyIdHex) { - mKeyIdHex = keyIdHex; - } - public boolean isRevoked() { return mRevoked; } @@ -172,10 +111,22 @@ public class ImportKeysListEntry implements Serializable, Parcelable { mRevoked = revoked; } + public boolean isRevokedOrExpired() { + return mRevoked || mExpired; + } + public Date getDate() { return mDate; } + public boolean isUpdated() { + return mUpdated; + } + + public void setUpdated(boolean updated) { + mUpdated = updated; + } + public void setDate(Date date) { mDate = date; } @@ -188,6 +139,10 @@ public class ImportKeysListEntry implements Serializable, Parcelable { mFingerprintHex = fingerprintHex; } + public void setFingerprint(byte[] fingerprint) { + mFingerprintHex = KeyFormattingUtils.convertFingerprintToHex(fingerprint); + } + public Integer getBitStrength() { return mBitStrength; } @@ -216,35 +171,38 @@ public class ImportKeysListEntry implements Serializable, Parcelable { mSecretKey = secretKey; } - public ArrayList getUserIds() { - return mUserIds; - } - - public void setUserIds(ArrayList userIds) { - mUserIds = userIds; - updateMergedUserIds(); - } - - public String getPrimaryUserId() { + public UserId getPrimaryUserId() { return mPrimaryUserId; } - public void setPrimaryUserId(String uid) { - mPrimaryUserId = uid; + public void setPrimaryUserId(String userId) { + mPrimaryUserId = KeyRing.splitUserId(userId); + } + + public void setPrimaryUserId(UserId primaryUserId) { + mPrimaryUserId = primaryUserId; + } + + public ParcelableHkpKeyserver getKeyserver() { + return mKeyserver; + } + + public void setKeyserver(ParcelableHkpKeyserver keyserver) { + mKeyserver = keyserver; } public String getKeybaseName() { return mKeybaseName; } - public String getFbUsername() { - return mFbUsername; - } - public void setKeybaseName(String keybaseName) { mKeybaseName = keybaseName; } + public String getFbUsername() { + return mFbUsername; + } + public void setFbUsername(String fbUsername) { mFbUsername = fbUsername; } @@ -257,16 +215,49 @@ public class ImportKeysListEntry implements Serializable, Parcelable { mQuery = query; } - public ArrayList getOrigins() { - return mOrigins; + public int hashCode() { + return mHashCode != null ? mHashCode : super.hashCode(); } - public void addOrigin(String origin) { - mOrigins.add(origin); + public List getUserIds() { + // To ensure choerency, use methods of this class to edit the list + return Collections.unmodifiableList(mUserIds); } - public HashMap> getMergedUserIds() { - return mMergedUserIds; + public ArrayList>> getSortedUserIds() { + if (mSortedUserIds == null) + sortMergedUserIds(); + + return mSortedUserIds; + } + + public void setUserIds(ArrayList userIds) { + mUserIds = userIds; + updateMergedUserIds(); + } + + public boolean addUserIds(List userIds) { + boolean modified = false; + for (String uid : userIds) { + if (!mUserIds.contains(uid)) { + mUserIds.add(uid); + modified = true; + } + } + + if (modified) + updateMergedUserIds(); + + return modified; + } + + public ArrayList getKeybaseUserIds() { + ArrayList keybaseUserIds = new ArrayList<>(); + for (String s : mUserIds) { + if (s.contains(":")) + keybaseUserIds.add(s); + } + return keybaseUserIds; } /** @@ -275,51 +266,44 @@ public class ImportKeysListEntry implements Serializable, Parcelable { public ImportKeysListEntry() { // keys from keyserver are always public keys; from keybase too mSecretKey = false; - // do not select by default - mSelected = false; + mUserIds = new ArrayList<>(); - mOrigins = new ArrayList<>(); } /** * Constructor based on key object, used for import from NFC, QR Codes, files */ - @SuppressWarnings("unchecked") - public ImportKeysListEntry(Context context, UncachedKeyRing ring) { - // selected is default - this.mSelected = true; - + public ImportKeysListEntry(Context ctx, UncachedKeyRing ring) { mSecretKey = ring.isSecret(); + UncachedPublicKey key = ring.getPublicKey(); - - mHashCode = key.hashCode(); - - mPrimaryUserId = key.getPrimaryUserIdWithFallback(); - mUserIds = key.getUnorderedUserIds(); - updateMergedUserIds(); - - // if there was no user id flagged as primary, use the first one - if (mPrimaryUserId == null) { - mPrimaryUserId = context.getString(R.string.user_id_none); - } - - mKeyId = key.getKeyId(); - mKeyIdHex = KeyFormattingUtils.convertKeyIdToHex(mKeyId); + setPrimaryUserId(key.getPrimaryUserIdWithFallback()); + setKeyId(key.getKeyId()); + setFingerprint(key.getFingerprint()); // NOTE: Dont use maybe methods for now, they can be wrong. mRevoked = false; //key.isMaybeRevoked(); mExpired = false; //key.isMaybeExpired(); - mFingerprintHex = KeyFormattingUtils.convertFingerprintToHex(key.getFingerprint()); + mBitStrength = key.getBitStrength(); mCurveOid = key.getCurveOid(); - final int algorithm = key.getAlgorithm(); - mAlgorithm = KeyFormattingUtils.getAlgorithmInfo(context, algorithm, mBitStrength, mCurveOid); + int algorithm = key.getAlgorithm(); + mAlgorithm = KeyFormattingUtils.getAlgorithmInfo(ctx, algorithm, mBitStrength, mCurveOid); + mHashCode = key.hashCode(); + + setUserIds(key.getUnorderedUserIds()); + + try { + byte[] encoded = ring.getEncoded(); + mParcelableKeyRing = new ParcelableKeyRing(encoded); + } catch (IOException ignored) { + } } - public void updateMergedUserIds() { + private void updateMergedUserIds() { mMergedUserIds = new HashMap<>(); for (String userId : mUserIds) { - OpenPgpUtils.UserId userIdSplit = KeyRing.splitUserId(userId); + UserId userIdSplit = KeyRing.splitUserId(userId); // TODO: comment field? @@ -341,6 +325,87 @@ public class ImportKeysListEntry implements Serializable, Parcelable { mMergedUserIds.put(userId, new HashSet()); } } + + mSortedUserIds = null; + } + + private void sortMergedUserIds() { + mSortedUserIds = new ArrayList<>(mMergedUserIds.entrySet()); + + Collections.sort(mSortedUserIds, new Comparator>>() { + @Override + public int compare(Map.Entry> entry1, + Map.Entry> entry2) { + + // sort keybase UserIds after non-Keybase + boolean e1IsKeybase = entry1.getKey().contains(":"); + boolean e2IsKeybase = entry2.getKey().contains(":"); + if (e1IsKeybase != e2IsKeybase) { + return (e1IsKeybase) ? 1 : -1; + } + return entry1.getKey().compareTo(entry2.getKey()); + } + }); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mParcelableKeyRing, flags); + dest.writeSerializable(mPrimaryUserId); + dest.writeStringList(mUserIds); + dest.writeSerializable(mMergedUserIds); + dest.writeByte((byte) (mRevoked ? 1 : 0)); + dest.writeByte((byte) (mExpired ? 1 : 0)); + dest.writeByte((byte) (mUpdated ? 1 : 0)); + dest.writeInt(mDate == null ? 0 : 1); + if (mDate != null) { + dest.writeLong(mDate.getTime()); + } + dest.writeString(mFingerprintHex); + dest.writeString(mKeyIdHex); + dest.writeInt(mBitStrength == null ? 0 : 1); + if (mBitStrength != null) { + dest.writeInt(mBitStrength); + } + dest.writeString(mAlgorithm); + dest.writeByte((byte) (mSecretKey ? 1 : 0)); + dest.writeParcelable(mKeyserver, flags); + dest.writeString(mKeybaseName); + dest.writeString(mFbUsername); + } + + public static final Creator CREATOR = new Creator() { + public ImportKeysListEntry createFromParcel(final Parcel source) { + ImportKeysListEntry vr = new ImportKeysListEntry(); + + vr.mParcelableKeyRing = source.readParcelable(ParcelableKeyRing.class.getClassLoader()); + vr.mPrimaryUserId = (UserId) source.readSerializable(); + vr.mUserIds = new ArrayList<>(); + source.readStringList(vr.mUserIds); + vr.mMergedUserIds = (HashMap>) source.readSerializable(); + vr.mRevoked = source.readByte() == 1; + vr.mExpired = source.readByte() == 1; + vr.mUpdated = source.readByte() == 1; + vr.mDate = source.readInt() != 0 ? new Date(source.readLong()) : null; + vr.mFingerprintHex = source.readString(); + vr.mKeyIdHex = source.readString(); + vr.mBitStrength = source.readInt() != 0 ? source.readInt() : null; + vr.mAlgorithm = source.readString(); + vr.mSecretKey = source.readByte() == 1; + vr.mKeyserver = source.readParcelable(ParcelableHkpKeyserver.class.getClassLoader()); + vr.mKeybaseName = source.readString(); + vr.mFbUsername = source.readString(); + + return vr; + } + + public ImportKeysListEntry[] newArray(final int size) { + return new ImportKeysListEntry[size]; + } + }; + + public int describeContents() { + return 0; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java index 6a87167fd..9a28ba900 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java @@ -18,8 +18,8 @@ package org.sufficientlysecure.keychain.keyimport; import com.textuality.keybase.lib.KeybaseException; -import com.textuality.keybase.lib.Match; import com.textuality.keybase.lib.KeybaseQuery; +import com.textuality.keybase.lib.Match; import com.textuality.keybase.lib.User; import org.sufficientlysecure.keychain.Constants; @@ -32,7 +32,6 @@ import java.util.ArrayList; import java.util.List; public class KeybaseKeyserver extends Keyserver { - public static final String ORIGIN = "keybase:keybase.io"; public KeybaseKeyserver() { } @@ -68,7 +67,6 @@ public class KeybaseKeyserver extends Keyserver { private ImportKeysListEntry makeEntry(Match match, String query) throws KeybaseException { final ImportKeysListEntry entry = new ImportKeysListEntry(); entry.setQuery(query); - entry.addOrigin(ORIGIN); entry.setRevoked(false); // keybase doesn’t say anything about revoked keys diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java index fd28c0221..f5970ef8a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java @@ -66,6 +66,10 @@ public abstract class Keyserver { private static final long serialVersionUID = 2703768928624654518L; } + public static class QueryNoEnabledSourceException extends QueryNeedsRepairException { + private static final long serialVersionUID = 2703768928624654519L; + } + public static class AddKeyException extends Exception { private static final long serialVersionUID = -507574859137295530L; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ParcelableHkpKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ParcelableHkpKeyserver.java index 25e2d0699..5174358c4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ParcelableHkpKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ParcelableHkpKeyserver.java @@ -22,6 +22,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.NonNull; +import android.support.annotation.NonNull; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; @@ -172,9 +173,11 @@ public class ParcelableHkpKeyserver extends Keyserver implements Parcelable { URI originalURI = new URI(keyserverUrl); String scheme = originalURI.getScheme(); - if (scheme == null - || (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme) - && !"hkp".equalsIgnoreCase(scheme) && !"hkps".equalsIgnoreCase(scheme))) { + if (scheme == null) { + throw new URISyntaxException("", "scheme null!"); + } + if (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme) + && !"hkp".equalsIgnoreCase(scheme) && !"hkps".equalsIgnoreCase(scheme)) { throw new URISyntaxException(scheme, "unsupported scheme!"); } @@ -269,7 +272,6 @@ public class ParcelableHkpKeyserver extends Keyserver implements Parcelable { while (matcher.find()) { final ImportKeysListEntry entry = new ImportKeysListEntry(); entry.setQuery(query); - entry.addOrigin(getHostID()); // group 1 contains the full fingerprint (v4) or the long key id if available // see https://bitbucket.org/skskeyserver/sks-keyserver/pull-request/12/fixes-for-machine-readable-indexes/diff @@ -293,10 +295,10 @@ public class ParcelableHkpKeyserver extends Keyserver implements Parcelable { int algorithmId = Integer.decode(matcher.group(2)); entry.setAlgorithm(KeyFormattingUtils.getAlgorithmInfo(algorithmId, bitSize, null)); - final long creationDate = Long.parseLong(matcher.group(4)); - final GregorianCalendar tmpGreg = new GregorianCalendar(TimeZone.getTimeZone("UTC")); - tmpGreg.setTimeInMillis(creationDate * 1000); - entry.setDate(tmpGreg.getTime()); + long creationDate = Long.parseLong(matcher.group(4)); + GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC")); + calendar.setTimeInMillis(creationDate * 1000); + entry.setDate(calendar.getTime()); } catch (NumberFormatException e) { Log.e(Constants.TAG, "Conversation for bit size, algorithm, or creation date failed.", e); // skip this key @@ -305,7 +307,18 @@ public class ParcelableHkpKeyserver extends Keyserver implements Parcelable { try { entry.setRevoked(matcher.group(6).contains("r")); - entry.setExpired(matcher.group(6).contains("e")); + boolean expired = matcher.group(6).contains("e"); + + // It may be expired even without flag, thus check expiration date + String expiration; + if (!expired && !(expiration = matcher.group(5)).isEmpty()) { + long expirationDate = Long.parseLong(expiration); + TimeZone timeZoneUTC = TimeZone.getTimeZone("UTC"); + GregorianCalendar calendar = new GregorianCalendar(timeZoneUTC); + calendar.setTimeInMillis(expirationDate * 1000); + expired = new GregorianCalendar(timeZoneUTC).compareTo(calendar) >= 0; + } + entry.setExpired(expired); } catch (NullPointerException e) { Log.e(Constants.TAG, "Check for revocation or expiry failed.", e); // skip this key @@ -338,6 +351,7 @@ public class ParcelableHkpKeyserver extends Keyserver implements Parcelable { } entry.setUserIds(userIds); entry.setPrimaryUserId(userIds.get(0)); + entry.setKeyserver(this); results.add(entry); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ParcelableKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ParcelableKeyRing.java index a94ce0dce..41a065314 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ParcelableKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ParcelableKeyRing.java @@ -21,7 +21,8 @@ package org.sufficientlysecure.keychain.keyimport; import android.os.Parcel; import android.os.Parcelable; -/** This class is a parcelable representation of either a keyring as raw data, +/** + * This class is a parcelable representation of either a keyring as raw data, * or a (unique) reference to one as a fingerprint, keyid, or keybase name. */ public class ParcelableKeyRing implements Parcelable { @@ -35,36 +36,23 @@ public class ParcelableKeyRing implements Parcelable { public final String mFbUsername; public ParcelableKeyRing(byte[] bytes) { - this(null, bytes, false); - } - - /** - * @param disAmbiguator useless parameter intended to distinguish this overloaded constructor - * for when null is passed as first two arguments - */ - public ParcelableKeyRing(String expectedFingerprint, byte[] bytes, boolean disAmbiguator) { - mBytes = bytes; - mExpectedFingerprint = expectedFingerprint; - mKeyIdHex = null; - mKeybaseName = null; - mFbUsername = null; - } - - public ParcelableKeyRing(String expectedFingerprint, String keyIdHex) { - mBytes = null; - mExpectedFingerprint = expectedFingerprint; - mKeyIdHex = keyIdHex; - mKeybaseName = null; - mFbUsername = null; + this(bytes, null, null, null, null); } public ParcelableKeyRing(String expectedFingerprint, String keyIdHex, String keybaseName, String fbUsername) { - mBytes = null; - mExpectedFingerprint = expectedFingerprint; - mKeyIdHex = keyIdHex; - mKeybaseName = keybaseName; - mFbUsername = fbUsername; + + this(null, expectedFingerprint, keyIdHex, keybaseName, fbUsername); + } + + public ParcelableKeyRing(byte[] bytes, String expectedFingerprint, String keyIdHex, + String keybaseName, String fbUsername) { + + this.mBytes = bytes; + this.mExpectedFingerprint = expectedFingerprint; + this.mKeyIdHex = keyIdHex; + this.mKeybaseName = keybaseName; + this.mFbUsername = fbUsername; } private ParcelableKeyRing(Parcel source) { @@ -78,6 +66,7 @@ public class ParcelableKeyRing implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeByteArray(mBytes); + dest.writeString(mExpectedFingerprint); dest.writeString(mKeyIdHex); dest.writeString(mKeybaseName); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/AsyncTaskResultWrapper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/AsyncTaskResultWrapper.java similarity index 96% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/AsyncTaskResultWrapper.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/AsyncTaskResultWrapper.java index 152629ef4..3f1c9a08b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/AsyncTaskResultWrapper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/AsyncTaskResultWrapper.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.ui.adapter; +package org.sufficientlysecure.keychain.keyimport.processing; import org.sufficientlysecure.keychain.operations.results.OperationResult; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/BytesLoaderState.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/BytesLoaderState.java new file mode 100644 index 000000000..03e4dbc14 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/BytesLoaderState.java @@ -0,0 +1,20 @@ +package org.sufficientlysecure.keychain.keyimport.processing; + +import android.net.Uri; + +public class BytesLoaderState implements LoaderState { + + public byte[] mKeyBytes; + public Uri mDataUri; + + public BytesLoaderState(byte[] keyBytes, Uri dataUri) { + mKeyBytes = keyBytes; + mDataUri = dataUri; + } + + @Override + public boolean isBasicModeSupported() { + return true; + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/CloudLoaderState.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/CloudLoaderState.java new file mode 100644 index 000000000..5d7e1f626 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/CloudLoaderState.java @@ -0,0 +1,20 @@ +package org.sufficientlysecure.keychain.keyimport.processing; + +import org.sufficientlysecure.keychain.util.Preferences; + +public class CloudLoaderState implements LoaderState { + + public Preferences.CloudSearchPrefs mCloudPrefs; + public String mServerQuery; + + public CloudLoaderState(String serverQuery, Preferences.CloudSearchPrefs cloudPrefs) { + mServerQuery = serverQuery; + mCloudPrefs = cloudPrefs; + } + + @Override + public boolean isBasicModeSupported() { + return false; + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/ImportKeysListCloudLoader.java similarity index 82% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/ImportKeysListCloudLoader.java index 5725989b4..dd59feff0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/ImportKeysListCloudLoader.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.ui.adapter; +package org.sufficientlysecure.keychain.keyimport.processing; import android.content.Context; import android.support.annotation.Nullable; @@ -25,6 +25,7 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.keyimport.CloudSearch; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.keyimport.Keyserver; +import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.results.GetKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; @@ -38,11 +39,9 @@ import java.util.ArrayList; public class ImportKeysListCloudLoader extends AsyncTaskLoader>> { - Context mContext; - - Preferences.CloudSearchPrefs mCloudPrefs; - String mServerQuery; + private Context mContext; + private CloudLoaderState mState; private ParcelableProxy mParcelableProxy; private ArrayList mEntryList = new ArrayList<>(); @@ -51,18 +50,18 @@ public class ImportKeysListCloudLoader /** * Searches a keyserver as specified in cloudPrefs, using an explicit proxy if passed * - * @param serverQuery string to search on servers for. If is a fingerprint, - * will enforce fingerprint check - * @param cloudPrefs contains keyserver to search on, whether to search on the keyserver, - * and whether to search keybase.io + * @param loaderState state containing the string to search on servers for (if it is a + * fingerprint, will enforce fingerprint check) and the keyserver to + * search on (whether to search on the keyserver, and whether to search + * keybase.io) * @param parcelableProxy explicit proxy to use. If null, will retrieve from preferences */ - public ImportKeysListCloudLoader(Context context, String serverQuery, Preferences.CloudSearchPrefs cloudPrefs, + public ImportKeysListCloudLoader(Context context, CloudLoaderState loaderState, @Nullable ParcelableProxy parcelableProxy) { + super(context); mContext = context; - mServerQuery = serverQuery; - mCloudPrefs = cloudPrefs; + mState = loaderState; mParcelableProxy = parcelableProxy; } @@ -70,18 +69,24 @@ public class ImportKeysListCloudLoader public AsyncTaskResultWrapper> loadInBackground() { mEntryListWrapper = new AsyncTaskResultWrapper<>(mEntryList, null); - if (mServerQuery == null) { + if (mState.mServerQuery == null) { Log.e(Constants.TAG, "mServerQuery is null!"); return mEntryListWrapper; } - if (mServerQuery.startsWith("0x") && mServerQuery.length() == 42) { + if (mState.mServerQuery.startsWith("0x") && mState.mServerQuery.length() == 42) { Log.d(Constants.TAG, "This search is based on a unique fingerprint. Enforce a fingerprint check!"); queryServer(true); } else { queryServer(false); } + // Now we have all the data needed to build the parcelable key ring for this key + for (ImportKeysListEntry e : mEntryList) { + e.setParcelableKeyRing(new ParcelableKeyRing(e.getFingerprintHex(), e.getKeyIdHex(), + e.getKeybaseName(), e.getFbUsername())); + } + return mEntryListWrapper; } @@ -133,15 +138,15 @@ public class ImportKeysListCloudLoader try { ArrayList searchResult = CloudSearch.search( - mServerQuery, - mCloudPrefs, + mState.mServerQuery, + mState.mCloudPrefs, proxy ); mEntryList.clear(); // add result to data if (enforceFingerprint) { - String fingerprint = mServerQuery.substring(2); + String fingerprint = mState.mServerQuery.substring(2); Log.d(Constants.TAG, "fingerprint: " + fingerprint); // query must return only one result! if (searchResult.size() == 1) { @@ -151,7 +156,6 @@ public class ImportKeysListCloudLoader * to enforce a check when the key is imported by KeychainService */ uniqueEntry.setFingerprintHex(fingerprint); - uniqueEntry.setSelected(true); mEntryList.add(uniqueEntry); } } else { @@ -175,6 +179,9 @@ public class ImportKeysListCloudLoader } else if (e instanceof Keyserver.QueryTooShortOrTooManyResponsesException) { error = GetKeyResult.RESULT_ERROR_TOO_SHORT_OR_TOO_MANY_RESPONSES; logType = OperationResult.LogType.MSG_GET_QUERY_TOO_SHORT_OR_TOO_MANY_RESPONSES; + } else if (e instanceof Keyserver.QueryNoEnabledSourceException) { + error = GetKeyResult.RESULT_ERROR_NO_ENABLED_SOURCE; + logType = OperationResult.LogType.MSG_GET_NO_ENABLED_SOURCE; } OperationResult.OperationLog log = new OperationResult.OperationLog(); log.add(logType, 0); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/ImportKeysListLoader.java similarity index 73% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/ImportKeysListLoader.java index df24e9877..f4afecc09 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/ImportKeysListLoader.java @@ -15,9 +15,26 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.ui.adapter; +package org.sufficientlysecure.keychain.keyimport.processing; +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.v4.content.AsyncTaskLoader; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; +import org.sufficientlysecure.keychain.operations.results.GetKeyResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow; +import org.sufficientlysecure.keychain.util.FileHelper; +import org.sufficientlysecure.keychain.util.InputData; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.PositionAwareInputStream; + import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.FileNotFoundException; @@ -25,40 +42,19 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; -import android.content.Context; -import android.support.annotation.NonNull; -import android.support.v4.content.AsyncTaskLoader; -import android.support.v4.util.LongSparseArray; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; -import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; -import org.sufficientlysecure.keychain.operations.results.GetKeyResult; -import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; -import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; -import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; -import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow; -import org.sufficientlysecure.keychain.ui.ImportKeysListFragment.BytesLoaderState; -import org.sufficientlysecure.keychain.util.FileHelper; -import org.sufficientlysecure.keychain.util.InputData; -import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.PositionAwareInputStream; - public class ImportKeysListLoader extends AsyncTaskLoader>> { - final Context mContext; - final BytesLoaderState mLoaderState; + private Context mContext; + private BytesLoaderState mState; - ArrayList mData = new ArrayList<>(); - LongSparseArray mParcelableRings = new LongSparseArray<>(); - AsyncTaskResultWrapper> mEntryListWrapper; + private ArrayList mData = new ArrayList<>(); + private AsyncTaskResultWrapper> mEntryListWrapper; - public ImportKeysListLoader(Context context, BytesLoaderState inputData) { + public ImportKeysListLoader(Context context, BytesLoaderState loaderState) { super(context); - this.mContext = context; - this.mLoaderState = inputData; + mContext = context; + mState = loaderState; } @Override @@ -73,13 +69,13 @@ public class ImportKeysListLoader mEntryListWrapper = new AsyncTaskResultWrapper<>(mData, getKeyResult); } - if (mLoaderState == null) { + if (mState == null) { Log.e(Constants.TAG, "Input data is null!"); return mEntryListWrapper; } try { - InputData inputData = getInputData(getContext(), mLoaderState); + InputData inputData = getInputData(mState); generateListOfKeyrings(inputData); } catch (FileNotFoundException e) { OperationLog log = new OperationLog(); @@ -109,16 +105,9 @@ public class ImportKeysListLoader super.cancelLoad(); } - @Override - public void deliverResult(AsyncTaskResultWrapper> data) { - super.deliverResult(data); - } - - public LongSparseArray getParcelableRings() { - return mParcelableRings; - } - - /** Reads all PGPKeyRing objects from the bytes of an InputData object. */ + /** + * Reads all PGPKeyRing objects from the bytes of an InputData object. + */ private void generateListOfKeyrings(InputData inputData) { PositionAwareInputStream progressIn = new PositionAwareInputStream( inputData.getInputStream()); @@ -131,10 +120,7 @@ public class ImportKeysListLoader // parse all keyrings IteratorWithIOThrow it = UncachedKeyRing.fromStream(bufferedInput); while (it.hasNext()) { - UncachedKeyRing ring = it.next(); - ImportKeysListEntry item = new ImportKeysListEntry(getContext(), ring); - mData.add(item); - mParcelableRings.put(item.hashCode(), new ParcelableKeyRing(ring.getEncoded())); + mData.add(new ImportKeysListEntry(mContext, it.next())); } } catch (IOException e) { Log.e(Constants.TAG, "IOException on parsing key file! Return NoValidKeysException!", e); @@ -147,13 +133,15 @@ public class ImportKeysListLoader } @NonNull - private static InputData getInputData(Context context, BytesLoaderState loaderState) throws FileNotFoundException { + private InputData getInputData(BytesLoaderState ls) + throws FileNotFoundException { + InputData inputData; - if (loaderState.mKeyBytes != null) { - inputData = new InputData(new ByteArrayInputStream(loaderState.mKeyBytes), loaderState.mKeyBytes.length); - } else if (loaderState.mDataUri != null) { - InputStream inputStream = context.getContentResolver().openInputStream(loaderState.mDataUri); - long length = FileHelper.getFileSize(context, loaderState.mDataUri, -1); + if (ls.mKeyBytes != null) { + inputData = new InputData(new ByteArrayInputStream(ls.mKeyBytes), ls.mKeyBytes.length); + } else if (ls.mDataUri != null) { + InputStream inputStream = mContext.getContentResolver().openInputStream(ls.mDataUri); + long length = FileHelper.getFileSize(mContext, ls.mDataUri, -1); inputData = new InputData(inputStream, length); } else { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/ImportKeysListener.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/ImportKeysListener.java new file mode 100644 index 000000000..55b20c561 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/ImportKeysListener.java @@ -0,0 +1,13 @@ +package org.sufficientlysecure.keychain.keyimport.processing; + +import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; + +import java.util.List; + +public interface ImportKeysListener extends ImportKeysResultListener { + + void loadKeys(LoaderState loaderState); + + void importKeys(List entries); + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/ImportKeysOperationCallback.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/ImportKeysOperationCallback.java new file mode 100644 index 000000000..aae47c590 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/ImportKeysOperationCallback.java @@ -0,0 +1,46 @@ +package org.sufficientlysecure.keychain.keyimport.processing; + +import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; +import org.sufficientlysecure.keychain.service.ImportKeyringParcel; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; + +public class ImportKeysOperationCallback implements + CryptoOperationHelper.Callback { + + private ImportKeysResultListener mResultListener; + private ImportKeyringParcel mKeyringParcel; + + public ImportKeysOperationCallback( + ImportKeysResultListener resultListener, + ImportKeyringParcel inputParcel + ) { + this.mResultListener = resultListener; + this.mKeyringParcel = inputParcel; + } + + @Override + public ImportKeyringParcel createOperationInput() { + return mKeyringParcel; + } + + @Override + public void onCryptoOperationSuccess(ImportKeyResult result) { + mResultListener.handleResult(result); + } + + @Override + public void onCryptoOperationCancelled() { + // do nothing + } + + @Override + public void onCryptoOperationError(ImportKeyResult result) { + mResultListener.handleResult(result); + } + + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/ImportKeysResultListener.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/ImportKeysResultListener.java new file mode 100644 index 000000000..35e30c257 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/ImportKeysResultListener.java @@ -0,0 +1,10 @@ +package org.sufficientlysecure.keychain.keyimport.processing; + +import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; +import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; + +public interface ImportKeysResultListener { + + void handleResult(ImportKeyResult result); + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/LoaderState.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/LoaderState.java new file mode 100644 index 000000000..098a2677d --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/LoaderState.java @@ -0,0 +1,13 @@ +package org.sufficientlysecure.keychain.keyimport.processing; + +public interface LoaderState { + + /** + * Basic mode includes ability to import all keys retrieved from the selected source + * This doesn't make sense for all sources (for example keyservers..) + * + * @return if currently selected source supports basic mode + */ + boolean isBasicModeSupported(); + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java index 078bf305d..f9f811d39 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java @@ -19,20 +19,6 @@ package org.sufficientlysecure.keychain.operations; -import java.io.IOException; -import java.net.Proxy; -import java.util.ArrayList; -import java.util.GregorianCalendar; -import java.util.Iterator; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorCompletionService; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - import android.content.Context; import android.support.annotation.NonNull; @@ -49,6 +35,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; +import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; @@ -57,14 +44,28 @@ import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; +import org.sufficientlysecure.keychain.util.IteratorWithSize; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableFileCache; -import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.ProgressScaler; import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; +import java.io.IOException; +import java.net.Proxy; +import java.util.ArrayList; +import java.util.GregorianCalendar; +import java.util.Iterator; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorCompletionService; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + /** * An operation class which implements high level import * operations. @@ -79,13 +80,13 @@ import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; * not include self certificates for user ids in the secret keyring. The import * method here will generally import keyrings in the order given by the * iterator, so this should be ensured beforehand. - * - * @see org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter#getSelectedEntries() */ public class ImportOperation extends BaseOperation { private static final int MAX_THREADS = 10; + public static final String CACHE_FILE_NAME = "key_import.pcl"; + public ImportOperation(Context context, ProviderHelper providerHelper, Progressable progressable) { super(context, providerHelper, progressable); @@ -98,20 +99,20 @@ public class ImportOperation extends BaseOperation { // Overloaded functions for using progressable supplied in constructor during import public ImportKeyResult serialKeyRingImport(Iterator entries, int num, - ParcelableHkpKeyserver hkpKeyserver, ParcelableProxy proxy) { - return serialKeyRingImport(entries, num, hkpKeyserver, mProgressable, proxy); + ParcelableHkpKeyserver keyserver, ParcelableProxy proxy, boolean skipSave) { + return serialKeyRingImport(entries, num, keyserver, mProgressable, proxy, skipSave); } @NonNull private ImportKeyResult serialKeyRingImport(ParcelableFileCache cache, - ParcelableHkpKeyserver hkpKeyserver, ParcelableProxy proxy) { + ParcelableHkpKeyserver keyserver, ParcelableProxy proxy, boolean skipSave) { // get entries from cached file try { IteratorWithSize it = cache.readCache(); int numEntries = it.getSize(); - return serialKeyRingImport(it, numEntries, hkpKeyserver, mProgressable, proxy); + return serialKeyRingImport(it, numEntries, keyserver, mProgressable, proxy, skipSave); } catch (IOException e) { // Special treatment here, we need a lot @@ -137,7 +138,7 @@ public class ImportOperation extends BaseOperation { @NonNull private ImportKeyResult serialKeyRingImport(Iterator entries, int num, ParcelableHkpKeyserver hkpKeyserver, Progressable progressable, - @NonNull ParcelableProxy proxy) { + @NonNull ParcelableProxy proxy, boolean skipSave) { if (progressable != null) { progressable.setProgress(R.string.progress_importing, 0, 100); } @@ -153,6 +154,8 @@ public class ImportOperation extends BaseOperation { int newKeys = 0, updatedKeys = 0, badKeys = 0, secret = 0; ArrayList importedMasterKeyIds = new ArrayList<>(); + ArrayList canKeyRings = new ArrayList<>(); + boolean cancelled = false; int position = 0; double progSteps = 100.0 / num; @@ -314,14 +317,14 @@ public class ImportOperation extends BaseOperation { // and https://github.com/open-keychain/open-keychain/issues/1480 synchronized (mProviderHelper) { mProviderHelper.clearLog(); + ProgressScaler progressScaler = new ProgressScaler(progressable, (int) (position * progSteps), + (int) ((position + 1) * progSteps), 100); if (key.isSecret()) { - result = mProviderHelper.saveSecretKeyRing(key, - new ProgressScaler(progressable, (int) (position * progSteps), - (int) ((position + 1) * progSteps), 100)); + result = mProviderHelper.saveSecretKeyRing(key, progressScaler, + canKeyRings, skipSave); } else { - result = mProviderHelper.savePublicKeyRing(key, - new ProgressScaler(progressable, (int) (position * progSteps), - (int) ((position + 1) * progSteps), 100), entry.mExpectedFingerprint); + result = mProviderHelper.savePublicKeyRing(key, progressScaler, + entry.mExpectedFingerprint, canKeyRings, skipSave); } } if (!result.success()) { @@ -337,7 +340,7 @@ public class ImportOperation extends BaseOperation { } importedMasterKeyIds.add(key.getMasterKeyId()); } - if (entry.mBytes == null) { + if (!skipSave && (entry.mBytes == null)) { // synonymous to isDownloadFromKeyserver. // If no byte data was supplied, import from keyserver took place // this prevents file imports being noted as keyserver imports @@ -360,7 +363,7 @@ public class ImportOperation extends BaseOperation { // synchronized on mProviderHelper to prevent // https://github.com/open-keychain/open-keychain/issues/1221 since a consolidate deletes // and re-inserts keys, which could conflict with a parallel db key update - if (secret > 0) { + if (!skipSave && (secret > 0)) { setPreventCancel(); ConsolidateResult result; synchronized (mProviderHelper) { @@ -418,8 +421,11 @@ public class ImportOperation extends BaseOperation { } } - return new ImportKeyResult(resultType, log, newKeys, updatedKeys, badKeys, secret, + ImportKeyResult result = new ImportKeyResult(resultType, log, newKeys, updatedKeys, badKeys, secret, importedMasterKeyIdsArray); + + result.setCanonicalizedKeyRings(canKeyRings); + return result; } @NonNull @@ -427,19 +433,18 @@ public class ImportOperation extends BaseOperation { public ImportKeyResult execute(ImportKeyringParcel importInput, CryptoInputParcel cryptoInput) { ArrayList keyList = importInput.mKeyList; ParcelableHkpKeyserver keyServer = importInput.mKeyserver; + boolean skipSave = importInput.mSkipSave; ImportKeyResult result; - if (keyList == null) {// import from file, do serially - ParcelableFileCache cache = new ParcelableFileCache<>(mContext, - "key_import.pcl"); - - result = serialKeyRingImport(cache, null, null); + ParcelableFileCache cache = + new ParcelableFileCache<>(mContext, CACHE_FILE_NAME); + result = serialKeyRingImport(cache, null, null, skipSave); } else { ParcelableProxy proxy; if (cryptoInput.getParcelableProxy() == null) { // explicit proxy not set - if(!OrbotHelper.isOrbotInRequiredState(mContext)) { + if (!OrbotHelper.isOrbotInRequiredState(mContext)) { // show dialog to enable/install dialog return new ImportKeyResult(null, RequiredInputParcel.createOrbotRequiredOperation(), cryptoInput); @@ -449,7 +454,7 @@ public class ImportOperation extends BaseOperation { proxy = cryptoInput.getParcelableProxy(); } - result = multiThreadedKeyImport(keyList.iterator(), keyList.size(), keyServer, proxy); + result = multiThreadedKeyImport(keyList, keyServer, proxy, skipSave); } ContactSyncAdapterService.requestContactsSync(); @@ -457,44 +462,43 @@ public class ImportOperation extends BaseOperation { } @NonNull - private ImportKeyResult multiThreadedKeyImport(@NonNull Iterator keyListIterator, - int totKeys, final ParcelableHkpKeyserver hkpKeyserver, - final ParcelableProxy proxy) { - Log.d(Constants.TAG, "Multi-threaded key import starting"); - KeyImportAccumulator accumulator = new KeyImportAccumulator(totKeys, mProgressable); + private ImportKeyResult multiThreadedKeyImport(ArrayList keyList, + final ParcelableHkpKeyserver keyServer, final ParcelableProxy proxy, + final boolean skipSave) { - final ProgressScaler ignoreProgressable = new ProgressScaler(); + Log.d(Constants.TAG, "Multi-threaded key import starting"); + + final Iterator keyListIterator = keyList.iterator(); + final int totKeys = keyList.size(); ExecutorService importExecutor = new ThreadPoolExecutor(0, MAX_THREADS, 30L, TimeUnit.SECONDS, new LinkedBlockingQueue()); - ExecutorCompletionService importCompletionService = new ExecutorCompletionService<>(importExecutor); while (keyListIterator.hasNext()) { // submit all key rings to be imported - - final ParcelableKeyRing pkRing = keyListIterator.next(); - Callable importOperationCallable = new Callable () { @Override public ImportKeyResult call() { - if (checkCancelled()) { return null; } ArrayList list = new ArrayList<>(); - list.add(pkRing); + list.add(keyListIterator.next()); + ProgressScaler ignoreProgressable = new ProgressScaler(); - return serialKeyRingImport(list.iterator(), 1, hkpKeyserver, ignoreProgressable, proxy); + return serialKeyRingImport(list.iterator(), 1, keyServer, ignoreProgressable, + proxy, skipSave); } }; importCompletionService.submit(importOperationCallable); } + KeyImportAccumulator accumulator = new KeyImportAccumulator(totKeys, mProgressable); while (!accumulator.isImportFinished()) { // accumulate the results of each import try { accumulator.accumulateKeyImport(importCompletionService.take().get()); @@ -511,7 +515,6 @@ public class ImportOperation extends BaseOperation { } } return accumulator.getConsolidatedResult(); - } /** @@ -519,10 +522,10 @@ public class ImportOperation extends BaseOperation { */ public static class KeyImportAccumulator { private OperationResult.OperationLog mImportLog = new OperationResult.OperationLog(); - Progressable mProgressable; + private Progressable mProgressable; private int mTotalKeys; private int mImportedKeys = 0; - ArrayList mImportedMasterKeyIds = new ArrayList<>(); + private ArrayList mImportedMasterKeyIds = new ArrayList<>(); private int mBadKeys = 0; private int mNewKeys = 0; private int mUpdatedKeys = 0; @@ -530,6 +533,8 @@ public class ImportOperation extends BaseOperation { private int mResultType = 0; private boolean mHasCancelledResult; + public ArrayList mCanonicalizedKeyRings; + /** * Accumulates keyring imports and updates the progressable whenever a new key is imported. * Also sets the progress to 0 on instantiation. @@ -544,6 +549,8 @@ public class ImportOperation extends BaseOperation { if (mProgressable != null) { mProgressable.setProgress(0, totalKeys); } + + mCanonicalizedKeyRings = new ArrayList<>(); } public void accumulateKeyImport(ImportKeyResult result) { @@ -575,6 +582,8 @@ public class ImportOperation extends BaseOperation { mImportedMasterKeyIds.add(masterKeyId); } + mCanonicalizedKeyRings.addAll(result.mCanonicalizedKeyRings); + // if any key import has been cancelled, set result type to cancelled // resultType is added to in getConsolidatedKayImport to account for remaining factors mResultType |= result.getResult() & ImportKeyResult.RESULT_CANCELLED; @@ -614,8 +623,11 @@ public class ImportOperation extends BaseOperation { masterKeyIds[i] = mImportedMasterKeyIds.get(i); } - return new ImportKeyResult(mResultType, mImportLog, mNewKeys, mUpdatedKeys, mBadKeys, - mSecret, masterKeyIds); + ImportKeyResult result = new ImportKeyResult(mResultType, mImportLog, mNewKeys, + mUpdatedKeys, mBadKeys, mSecret, masterKeyIds); + + result.setCanonicalizedKeyRings(mCanonicalizedKeyRings); + return result; } public boolean isImportFinished() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/GetKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/GetKeyResult.java index 76ffaff4a..c0eab6381 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/GetKeyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/GetKeyResult.java @@ -44,13 +44,14 @@ public class GetKeyResult extends InputPendingResult { super(log, requiredInput, cryptoInputParcel); } - public static final int RESULT_ERROR_NO_VALID_KEYS = RESULT_ERROR + (1<<4); - public static final int RESULT_ERROR_NO_PGP_PARTS = RESULT_ERROR + (2<<4); - public static final int RESULT_ERROR_QUERY_TOO_SHORT = RESULT_ERROR + (3<<4); - public static final int RESULT_ERROR_TOO_MANY_RESPONSES = RESULT_ERROR + (4<<4); - public static final int RESULT_ERROR_TOO_SHORT_OR_TOO_MANY_RESPONSES = RESULT_ERROR + (5<<4); - public static final int RESULT_ERROR_QUERY_FAILED = RESULT_ERROR + (6<<4); - public static final int RESULT_ERROR_FILE_NOT_FOUND = RESULT_ERROR + (7<<4); + public static final int RESULT_ERROR_NO_VALID_KEYS = RESULT_ERROR + (1 << 4); + public static final int RESULT_ERROR_NO_PGP_PARTS = RESULT_ERROR + (2 << 4); + public static final int RESULT_ERROR_QUERY_TOO_SHORT = RESULT_ERROR + (3 << 4); + public static final int RESULT_ERROR_TOO_MANY_RESPONSES = RESULT_ERROR + (4 << 4); + public static final int RESULT_ERROR_TOO_SHORT_OR_TOO_MANY_RESPONSES = RESULT_ERROR + (5 << 4); + public static final int RESULT_ERROR_QUERY_FAILED = RESULT_ERROR + (6 << 4); + public static final int RESULT_ERROR_FILE_NOT_FOUND = RESULT_ERROR + (7 << 4); + public static final int RESULT_ERROR_NO_ENABLED_SOURCE = RESULT_ERROR + (8 << 4); public GetKeyResult(Parcel source) { super(source); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java index 5f5090bee..71072f029 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ImportKeyResult.java @@ -23,6 +23,7 @@ import android.content.Intent; import android.os.Parcel; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.LogDisplayActivity; @@ -32,11 +33,16 @@ import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener; import org.sufficientlysecure.keychain.ui.util.Notify.Showable; import org.sufficientlysecure.keychain.ui.util.Notify.Style; +import java.util.ArrayList; + public class ImportKeyResult extends InputPendingResult { public final int mNewKeys, mUpdatedKeys, mBadKeys, mSecret; public final long[] mImportedMasterKeyIds; + // NOT PARCELED + public ArrayList mCanonicalizedKeyRings; + // At least one new key public static final int RESULT_OK_NEWKEYS = 8; // At least one updated key @@ -107,6 +113,10 @@ public class ImportKeyResult extends InputPendingResult { mImportedMasterKeyIds = new long[]{}; } + public void setCanonicalizedKeyRings(ArrayList canonicalizedKeyRings) { + this.mCanonicalizedKeyRings = canonicalizedKeyRings; + } + @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); @@ -128,7 +138,6 @@ public class ImportKeyResult extends InputPendingResult { }; public Showable createNotify(final Activity activity) { - int resultType = getResult(); String str; @@ -204,7 +213,6 @@ public class ImportKeyResult extends InputPendingResult { activity.startActivity(intent); } }, R.string.snackbar_details); - } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index be736d785..7a2286fdc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -818,6 +818,7 @@ public abstract class OperationResult implements Parcelable { MSG_GET_QUERY_TOO_SHORT_OR_TOO_MANY_RESPONSES (LogLevel.ERROR, R.string.msg_get_query_too_short_or_too_many_responses), MSG_GET_QUERY_FAILED (LogLevel.ERROR, R.string.msg_download_query_failed), MSG_GET_FILE_NOT_FOUND (LogLevel.ERROR, R.string.msg_get_file_not_found), + MSG_GET_NO_ENABLED_SOURCE (LogLevel.ERROR, R.string.msg_get_no_enabled_source), MSG_DEL_ERROR_EMPTY (LogLevel.ERROR, R.string.msg_del_error_empty), MSG_DEL_ERROR_MULTI_SECRET (LogLevel.ERROR, R.string.msg_del_error_multi_secret), diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java index f1a57461f..2075f6b58 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java @@ -29,18 +29,17 @@ import java.io.OutputStream; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; -import java.util.Iterator; import java.util.Set; -/** A generic wrapped PGPKeyRing object. - * +/** + * A generic wrapped PGPKeyRing object. + *

* This class provides implementations for all basic getters which both * PublicKeyRing and SecretKeyRing have in common. To make the wrapped keyring * class typesafe in implementing subclasses, the field is stored in the * implementing class, providing properly typed access through the getRing * getter method. - * */ public abstract class CanonicalizedKeyRing extends KeyRing { @@ -80,16 +79,24 @@ public abstract class CanonicalizedKeyRing extends KeyRing { public boolean isRevoked() { // Is the master key revoked? - return getRing().getPublicKey().isRevoked(); + return getRing().getPublicKey().hasRevocation(); + } + + public Date getCreationDate() { + return getPublicKey().getCreationTime(); + } + + public Date getExpirationDate() { + return getPublicKey().getExpiryTime(); } public boolean isExpired() { // Is the master key expired? - Date creationDate = getPublicKey().getCreationTime(); - Date expiryDate = getPublicKey().getExpiryTime(); + Date creationDate = getCreationDate(); + Date expirationDate = getExpirationDate(); Date now = new Date(); - return creationDate.after(now) || (expiryDate != null && expiryDate.before(now)); + return creationDate.after(now) || (expirationDate != null && expirationDate.before(now)); } public boolean canCertify() throws PgpKeyNotFoundException { @@ -98,7 +105,7 @@ public abstract class CanonicalizedKeyRing extends KeyRing { public Set getEncryptIds() { HashSet result = new HashSet<>(); - for(CanonicalizedPublicKey key : publicKeyIterator()) { + for (CanonicalizedPublicKey key : publicKeyIterator()) { if (key.canEncrypt() && key.isValid()) { result.add(key.getKeyId()); } @@ -107,7 +114,7 @@ public abstract class CanonicalizedKeyRing extends KeyRing { } public long getEncryptId() throws PgpKeyNotFoundException { - for(CanonicalizedPublicKey key : publicKeyIterator()) { + for (CanonicalizedPublicKey key : publicKeyIterator()) { if (key.canEncrypt() && key.isValid()) { return key.getKeyId(); } @@ -119,7 +126,7 @@ public abstract class CanonicalizedKeyRing extends KeyRing { try { getEncryptId(); return true; - } catch(PgpKeyNotFoundException e) { + } catch (PgpKeyNotFoundException e) { return false; } } @@ -128,8 +135,10 @@ public abstract class CanonicalizedKeyRing extends KeyRing { getRing().encode(stream); } - /** Returns an UncachedKeyRing which wraps the same data as this ring. This method should - * only be used */ + /** + * Returns an UncachedKeyRing which wraps the same data as this ring. This method should + * only be used + */ public UncachedKeyRing getUncachedKeyRing() { return new UncachedKeyRing(getRing()); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java index 604a5a027..019c5568a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java @@ -173,7 +173,7 @@ public class CachedPublicKeyRing extends KeyRing { Object data = mProviderHelper.getGenericData(mUri, KeychainContract.KeyRings.VERIFIED, ProviderHelper.FIELD_TYPE_INTEGER); - return (Integer) data; + return ((Long) data).intValue(); } catch(ProviderHelper.NotFoundException e) { throw new PgpKeyNotFoundException(e); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java index 6eda21d2c..c95a8f283 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -40,6 +40,7 @@ import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; +import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; @@ -61,9 +62,9 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeys; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.IterableIterator; +import org.sufficientlysecure.keychain.util.IteratorWithSize; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableFileCache; -import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.ProgressFixedScaler; import org.sufficientlysecure.keychain.util.ProgressScaler; @@ -276,7 +277,7 @@ public class ProviderHelper { public ArrayList getConfirmedUserIds(long masterKeyId) throws NotFoundException { Cursor cursor = mContentResolver.query(UserPackets.buildUserIdsUri(masterKeyId), - new String[]{ UserPackets.USER_ID }, UserPackets.VERIFIED + " = " + Certs.VERIFIED_SECRET, null, null + new String[]{UserPackets.USER_ID}, UserPackets.VERIFIED + " = " + Certs.VERIFIED_SECRET, null, null ); if (cursor == null) { throw new NotFoundException("Key id for requested user ids not found"); @@ -476,7 +477,7 @@ public class ProviderHelper { String userId = Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId); UserPacketItem item = new UserPacketItem(); uids.add(item); - OpenPgpUtils.UserId splitUserId = KeyRing.splitUserId(userId); + OpenPgpUtils.UserId splitUserId = KeyRing.splitUserId(userId); item.userId = userId; item.name = splitUserId.name; item.email = splitUserId.email; @@ -794,7 +795,7 @@ public class ProviderHelper { } // if one is *trusted* but the other isn't, that one comes first // this overrides the primary attribute, even! - if ( (trustedCerts.size() == 0) != (o.trustedCerts.size() == 0) ) { + if ((trustedCerts.size() == 0) != (o.trustedCerts.size() == 0)) { return trustedCerts.size() > o.trustedCerts.size() ? -1 : 1; } // if one key is primary but the other isn't, the primary one always comes first @@ -903,17 +904,16 @@ public class ProviderHelper { } - public SaveKeyringResult savePublicKeyRing(UncachedKeyRing keyRing) { - return savePublicKeyRing(keyRing, new ProgressScaler(), null); - } - /** * Save a public keyring into the database. *

* This is a high level method, which takes care of merging all new information into the old and * keep public and secret keyrings in sync. */ - public SaveKeyringResult savePublicKeyRing(UncachedKeyRing publicRing, Progressable progress, String expectedFingerprint) { + public SaveKeyringResult savePublicKeyRing(UncachedKeyRing publicRing, Progressable progress, + String expectedFingerprint, + ArrayList canKeyRings, + boolean skipSave) { try { long masterKeyId = publicRing.getMasterKeyId(); @@ -926,10 +926,12 @@ public class ProviderHelper { } CanonicalizedPublicKeyRing canPublicRing; + boolean alreadyExists = false; // If there is an old keyring, merge it try { UncachedKeyRing oldPublicRing = getCanonicalizedPublicKeyRing(masterKeyId).getUncachedKeyRing(); + alreadyExists = true; // Merge data from new public ring into the old one log(LogType.MSG_IP_MERGE_PUBLIC); @@ -945,6 +947,7 @@ public class ProviderHelper { if (canPublicRing == null) { return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); } + if (canKeyRings != null) canKeyRings.add(canPublicRing); // Early breakout if nothing changed if (Arrays.hashCode(publicRing.getEncoded()) @@ -960,7 +963,7 @@ public class ProviderHelper { if (canPublicRing == null) { return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); } - + if (canKeyRings != null) canKeyRings.add(canPublicRing); } // If there is a secret key, merge new data (if any) and save the key for later @@ -997,29 +1000,51 @@ public class ProviderHelper { } } - int result = saveCanonicalizedPublicKeyRing(canPublicRing, progress, canSecretRing != null); + int result; + if (!skipSave) { + result = saveCanonicalizedPublicKeyRing(canPublicRing, progress, canSecretRing != null); + } else { + result = SaveKeyringResult.SAVED_PUBLIC + | (alreadyExists ? SaveKeyringResult.UPDATED : 0); + } // Save the saved keyring (if any) if (canSecretRing != null) { progress.setProgress(LogType.MSG_IP_REINSERT_SECRET.getMsgId(), 90, 100); - int secretResult = saveCanonicalizedSecretKeyRing(canSecretRing); + + int secretResult; + if (!skipSave) { + secretResult = saveCanonicalizedSecretKeyRing(canSecretRing); + } else { + secretResult = SaveKeyringResult.SAVED_SECRET; + } + if ((secretResult & SaveKeyringResult.RESULT_ERROR) != SaveKeyringResult.RESULT_ERROR) { result |= SaveKeyringResult.SAVED_SECRET; } } return new SaveKeyringResult(result, mLog, canSecretRing); - } catch (IOException e) { log(LogType.MSG_IP_ERROR_IO_EXC); return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); } finally { mIndent -= 1; } - } - public SaveKeyringResult saveSecretKeyRing(UncachedKeyRing secretRing, Progressable progress) { + public SaveKeyringResult savePublicKeyRing(UncachedKeyRing publicRing, Progressable progress, + String expectedFingerprint) { + return savePublicKeyRing(publicRing, progress, expectedFingerprint, null, false); + } + + public SaveKeyringResult savePublicKeyRing(UncachedKeyRing keyRing) { + return savePublicKeyRing(keyRing, new ProgressScaler(), null); + } + + public SaveKeyringResult saveSecretKeyRing(UncachedKeyRing secretRing, Progressable progress, + ArrayList canKeyRings, + boolean skipSave) { try { long masterKeyId = secretRing.getMasterKeyId(); @@ -1032,10 +1057,12 @@ public class ProviderHelper { } CanonicalizedSecretKeyRing canSecretRing; + boolean alreadyExists = false; // If there is an old secret key, merge it. try { UncachedKeyRing oldSecretRing = getCanonicalizedSecretKeyRing(masterKeyId).getUncachedKeyRing(); + alreadyExists = true; // Merge data from new secret ring into old one log(LogType.MSG_IS_MERGE_SECRET); @@ -1052,6 +1079,7 @@ public class ProviderHelper { if (canSecretRing == null) { return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); } + if (canKeyRings != null) canKeyRings.add(canSecretRing); // Early breakout if nothing changed if (Arrays.hashCode(secretRing.getEncoded()) @@ -1083,7 +1111,7 @@ public class ProviderHelper { return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); } } - + if (canKeyRings != null) canKeyRings.add(canSecretRing); } // Merge new data into public keyring as well, if there is any @@ -1109,25 +1137,38 @@ public class ProviderHelper { return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); } - int result; + int publicResult; + if (!skipSave) { + publicResult = saveCanonicalizedPublicKeyRing(canPublicRing, progress, true); + } else { + publicResult = SaveKeyringResult.SAVED_PUBLIC; + } - result = saveCanonicalizedPublicKeyRing(canPublicRing, progress, true); - if ((result & SaveKeyringResult.RESULT_ERROR) == SaveKeyringResult.RESULT_ERROR) { + if ((publicResult & SaveKeyringResult.RESULT_ERROR) == SaveKeyringResult.RESULT_ERROR) { return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); } progress.setProgress(LogType.MSG_IP_REINSERT_SECRET.getMsgId(), 90, 100); - result = saveCanonicalizedSecretKeyRing(canSecretRing); + + int result; + if (!skipSave) { + result = saveCanonicalizedSecretKeyRing(canSecretRing); + } else { + result = SaveKeyringResult.SAVED_SECRET + | (alreadyExists ? SaveKeyringResult.UPDATED : 0); + } return new SaveKeyringResult(result, mLog, canSecretRing); - } catch (IOException e) { log(LogType.MSG_IS_ERROR_IO_EXC); return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); } finally { mIndent -= 1; } + } + public SaveKeyringResult saveSecretKeyRing(UncachedKeyRing secretRing, Progressable progress) { + return saveSecretKeyRing(secretRing, progress, null, false); } @NonNull @@ -1350,7 +1391,7 @@ public class ProviderHelper { ImportKeyResult result = new ImportOperation(mContext, this, new ProgressFixedScaler(progress, 10, 25, 100, R.string.progress_con_reimport)) - .serialKeyRingImport(itSecrets, numSecrets, null, null); + .serialKeyRingImport(itSecrets, numSecrets, null, null, false); log.add(result, indent); } else { log.add(LogType.MSG_CON_REIMPORT_SECRET_SKIP, indent); @@ -1378,7 +1419,7 @@ public class ProviderHelper { ImportKeyResult result = new ImportOperation(mContext, this, new ProgressFixedScaler(progress, 25, 99, 100, R.string.progress_con_reimport)) - .serialKeyRingImport(itPublics, numPublics, null, null); + .serialKeyRingImport(itPublics, numPublics, null, null, false); log.add(result, indent); // re-insert our backed up list of updated key times // TODO: can this cause issues in case a public key re-import failed? diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteImportKeysActivity.java index ff7138231..4d8113e3c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteImportKeysActivity.java @@ -21,9 +21,7 @@ import android.content.Intent; import android.os.Bundle; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; -import org.sufficientlysecure.keychain.remote.CryptoInputParcelCacheService; import org.sufficientlysecure.keychain.ui.ImportKeysActivity; -import org.sufficientlysecure.keychain.ui.SecurityTokenOperationActivity; public class RemoteImportKeysActivity extends ImportKeysActivity { @@ -39,7 +37,7 @@ public class RemoteImportKeysActivity extends ImportKeysActivity { } @Override - protected void handleResult(ImportKeyResult result) { + public void handleResult(ImportKeyResult result) { setResult(RESULT_OK, mPendingIntentData); finish(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ImportKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ImportKeyringParcel.java index ad609d72a..050e126a9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ImportKeyringParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ImportKeyringParcel.java @@ -27,15 +27,23 @@ import org.sufficientlysecure.keychain.keyimport.ParcelableHkpKeyserver; import java.util.ArrayList; public class ImportKeyringParcel implements Parcelable { - // if null, keys are expected to be read from a cache file in ImportExportOperations + // If null, keys are expected to be read from a cache file in ImportExportOperations public ArrayList mKeyList; public ParcelableHkpKeyserver mKeyserver; // must be set if keys are to be imported from a keyserver + // If false, don't save the key, only return it as part of result + public boolean mSkipSave = false; + public ImportKeyringParcel(ArrayList keyList, ParcelableHkpKeyserver keyserver) { mKeyList = keyList; mKeyserver = keyserver; } + public ImportKeyringParcel(ArrayList keyList, ParcelableHkpKeyserver keyserver, boolean skipSave) { + this(keyList, keyserver); + mSkipSave = skipSave; + } + protected ImportKeyringParcel(Parcel in) { if (in.readByte() == 0x01) { mKeyList = new ArrayList<>(); @@ -44,6 +52,7 @@ public class ImportKeyringParcel implements Parcelable { mKeyList = null; } mKeyserver = in.readParcelable(ParcelableHkpKeyserver.class.getClassLoader()); + mSkipSave = in.readInt() != 0; } @Override @@ -60,6 +69,7 @@ public class ImportKeyringParcel implements Parcelable { dest.writeList(mKeyList); } dest.writeParcelable(mKeyserver, flags); + dest.writeInt(mSkipSave ? 1 : 0); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java index ed66283e8..99b3ef7a6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java @@ -465,7 +465,7 @@ public class KeyserverSyncAdapterService extends Service { String hexKeyId = KeyFormattingUtils .convertKeyIdToHex(keyId); // we aren't updating from keybase as of now - keyList.add(new ParcelableKeyRing(fingerprint, hexKeyId)); + keyList.add(new ParcelableKeyRing(fingerprint, hexKeyId, null, null)); } keyCursor.close(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyNameFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyNameFragment.java index 3332b9cf9..6aff7c05b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyNameFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyNameFragment.java @@ -18,6 +18,7 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; +import android.content.Context; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; @@ -80,8 +81,8 @@ public class CreateKeyNameFragment extends Fragment { } @Override - public void onAttach(Activity activity) { - super.onAttach(activity); + public void onAttach(Context context) { + super.onAttach(context); mCreateKeyActivity = (CreateKeyActivity) getActivity(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyPassphraseFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyPassphraseFragment.java index d858fd6ec..0a43431b9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyPassphraseFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyPassphraseFragment.java @@ -164,8 +164,8 @@ public class CreateKeyPassphraseFragment extends Fragment { } @Override - public void onAttach(Activity activity) { - super.onAttach(activity); + public void onAttach(Context context) { + super.onAttach(context); mCreateKeyActivity = (CreateKeyActivity) getActivity(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java index c62ec97e7..007049e15 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java @@ -18,6 +18,7 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; +import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.Fragment; @@ -130,8 +131,8 @@ public class CreateKeyStartFragment extends Fragment { } @Override - public void onAttach(Activity activity) { - super.onAttach(activity); + public void onAttach(Context context) { + super.onAttach(context); mCreateKeyActivity = (CreateKeyActivity) getActivity(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenBlankFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenBlankFragment.java index 08441c199..729cb7dea 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenBlankFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenBlankFragment.java @@ -18,6 +18,7 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; +import android.content.Context; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; @@ -75,8 +76,8 @@ public class CreateSecurityTokenBlankFragment extends Fragment { } @Override - public void onAttach(Activity activity) { - super.onAttach(activity); + public void onAttach(Context context) { + super.onAttach(context); mCreateKeyActivity = (CreateKeyActivity) getActivity(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java index fd01c0ad2..2ebd2e944 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java @@ -18,11 +18,8 @@ package org.sufficientlysecure.keychain.ui; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.ArrayList; - import android.app.Activity; +import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.Fragment; @@ -36,6 +33,7 @@ import android.widget.TextView; import org.bouncycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; +import org.sufficientlysecure.keychain.keyimport.processing.CloudLoaderState; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; @@ -48,6 +46,10 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.keyimport.ParcelableHkpKeyserver; import org.sufficientlysecure.keychain.util.Preferences; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; + public class CreateSecurityTokenImportResetFragment extends QueueingCryptoOperationFragment @@ -194,8 +196,8 @@ public class CreateSecurityTokenImportResetFragment } @Override - public void onAttach(Activity activity) { - super.onAttach(activity); + public void onAttach(Context context) { + super.onAttach(context); mCreateKeyActivity = (CreateKeyActivity) getActivity(); } @@ -211,14 +213,14 @@ public class CreateSecurityTokenImportResetFragment } public void refreshSearch() { - mListFragment.loadNew(new ImportKeysListFragment.CloudLoaderState("0x" + mTokenFingerprint, + mListFragment.loadState(new CloudLoaderState("0x" + mTokenFingerprint, Preferences.getPreferences(getActivity()).getCloudSearchPrefs())); } public void importKey() { ArrayList keyList = new ArrayList<>(); - keyList.add(new ParcelableKeyRing(mTokenFingerprint, null)); + keyList.add(new ParcelableKeyRing(mTokenFingerprint, null, null, null)); mKeyList = keyList; mKeyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenPinFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenPinFragment.java index a6ecef4e6..1033d373e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenPinFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenPinFragment.java @@ -167,8 +167,8 @@ public class CreateSecurityTokenPinFragment extends Fragment { } @Override - public void onAttach(Activity activity) { - super.onAttach(activity); + public void onAttach(Context context) { + super.onAttach(context); mCreateKeyActivity = (CreateKeyActivity) getActivity(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java index 4521ae659..ad0e11d94 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java @@ -17,8 +17,6 @@ package org.sufficientlysecure.keychain.ui; -import java.util.ArrayList; - import android.app.Activity; import android.content.Intent; import android.database.Cursor; @@ -59,6 +57,8 @@ import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.keyimport.ParcelableHkpKeyserver; import org.sufficientlysecure.keychain.util.Preferences; +import java.util.ArrayList; + public abstract class DecryptFragment extends Fragment implements LoaderManager.LoaderCallbacks { public static final int LOADER_ID_UNIFIED = 0; @@ -145,7 +145,7 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager. { ParcelableKeyRing keyEntry = new ParcelableKeyRing(null, - KeyFormattingUtils.convertKeyIdToHex(unknownKeyId)); + KeyFormattingUtils.convertKeyIdToHex(unknownKeyId), null, null); ArrayList selectedEntries = new ArrayList<>(); selectedEntries.add(keyEntry); @@ -320,7 +320,7 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager. mSignatureEmail.setText(userIdSplit.email); } else { mSignatureEmail.setText(KeyFormattingUtils.beautifyKeyIdWithPrefix( - getActivity(), mSignatureResult.getKeyId())); + mSignatureResult.getKeyId())); } // NOTE: Don't use revoked and expired fields from database, they don't show @@ -430,7 +430,7 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager. mSignatureEmail.setText(userIdSplit.email); } else { mSignatureEmail.setText(KeyFormattingUtils.beautifyKeyIdWithPrefix( - getActivity(), mSignatureResult.getKeyId())); + mSignatureResult.getKeyId())); } switch (mSignatureResult.getResult()) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java index c43894b79..134db8fd4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -68,6 +68,7 @@ import android.widget.Toast; import android.widget.ViewAnimator; import com.cocosw.bottomsheet.BottomSheet; + import org.openintents.openpgp.OpenPgpMetadata; import org.openintents.openpgp.OpenPgpSignatureResult; import org.sufficientlysecure.keychain.BuildConfig; @@ -98,24 +99,24 @@ import org.sufficientlysecure.keychain.keyimport.ParcelableHkpKeyserver; import org.sufficientlysecure.keychain.util.Preferences; -/** Displays a list of decrypted inputs. - * +/** + * Displays a list of decrypted inputs. + *

* This class has a complex control flow to manage its input URIs. Each URI * which is in mInputUris is also in exactly one of mPendingInputUris, * mCancelledInputUris, mCurrentInputUri, or a key in mInputDataResults. - * + *

* Processing of URIs happens using a looping approach: * - There is always exactly one method running which works on mCurrentInputUri * - Processing starts in cryptoOperation(), which pops a new mCurrentInputUri - * from the list of mPendingInputUris. + * from the list of mPendingInputUris. * - Once a mCurrentInputUri is finished processing, it should be set to null and - * control handed back to cryptoOperation() + * control handed back to cryptoOperation() * - Control flow can move through asynchronous calls, and resume in callbacks - * like onActivityResult() or onPermissionRequestResult(). - * + * like onActivityResult() or onPermissionRequestResult(). */ public class DecryptListFragment - extends QueueingCryptoOperationFragment + extends QueueingCryptoOperationFragment implements OnMenuItemClickListener { public static final String ARG_INPUT_URIS = "input_uris"; @@ -189,7 +190,7 @@ public class DecryptListFragment outState.putParcelableArrayList(ARG_INPUT_URIS, mInputUris); - HashMap results = new HashMap<>(mInputUris.size()); + HashMap results = new HashMap<>(mInputUris.size()); for (Uri uri : mInputUris) { if (mPendingInputUris.contains(uri)) { continue; @@ -219,7 +220,7 @@ public class DecryptListFragment ArrayList inputUris = getArguments().getParcelableArrayList(ARG_INPUT_URIS); ArrayList cancelledUris = args.getParcelableArrayList(ARG_CANCELLED_URIS); - ParcelableHashMap results = args.getParcelable(ARG_RESULTS); + ParcelableHashMap results = args.getParcelable(ARG_RESULTS); mCanDelete = args.getBoolean(ARG_CAN_DELETE, false); @@ -231,11 +232,11 @@ public class DecryptListFragment private void displayInputUris( ArrayList inputUris, ArrayList cancelledUris, - HashMap results) { + HashMap results) { mInputUris = inputUris; mCurrentInputUri = null; - mInputDataResults = results != null ? results : new HashMap(inputUris.size()); + mInputDataResults = results != null ? results : new HashMap(inputUris.size()); mCancelledInputUris = cancelledUris != null ? cancelledUris : new ArrayList(); mPendingInputUris = new ArrayList<>(); @@ -295,7 +296,7 @@ public class DecryptListFragment String filename = metadata.getFilename(); if (TextUtils.isEmpty(filename)) { String ext = MimeTypeMap.getSingleton().getExtensionFromMimeType(metadata.getMimeType()); - filename = "decrypted" + (ext != null ? "."+ext : ""); + filename = "decrypted" + (ext != null ? "." + ext : ""); } // requires >=kitkat @@ -395,7 +396,7 @@ public class DecryptListFragment } - HashMap mIconCache = new HashMap<>(); + HashMap mIconCache = new HashMap<>(); private void processResult(final Uri uri) { @@ -562,7 +563,7 @@ public class DecryptListFragment Intent chooserIntent = Intent.createChooser(intent, getString(R.string.intent_show)); chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, - new Parcelable[] { internalIntent }); + new Parcelable[]{internalIntent}); startActivity(chooserIntent); @@ -606,7 +607,7 @@ public class DecryptListFragment .putExtra(DisplayTextActivity.EXTRA_METADATA, metadata), BuildConfig.APPLICATION_ID, R.string.view_internal, R.mipmap.ic_launcher); chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, - new Parcelable[] { internalIntent }); + new Parcelable[]{internalIntent}); } startActivity(chooserIntent); @@ -633,7 +634,7 @@ public class DecryptListFragment Log.d(Constants.TAG, "mCurrentInputUri=" + mCurrentInputUri); - if ( ! checkAndRequestReadPermission(activity, mCurrentInputUri)) { + if (!checkAndRequestReadPermission(activity, mCurrentInputUri)) { return null; } @@ -645,15 +646,15 @@ public class DecryptListFragment /** * Request READ_EXTERNAL_STORAGE permission on Android >= 6.0 to read content from "file" Uris. - * + *

* This method returns true on Android < 6, or if permission is already granted. It * requests the permission and returns false otherwise, taking over responsibility * for mCurrentInputUri. - * + *

* see https://commonsware.com/blog/2015/10/07/runtime-permissions-files-action-send.html */ private boolean checkAndRequestReadPermission(Activity activity, final Uri uri) { - if ( ! ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { + if (!ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { return true; } @@ -668,7 +669,7 @@ public class DecryptListFragment } requestPermissions( - new String[] { Manifest.permission.READ_EXTERNAL_STORAGE }, + new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_PERMISSION_READ_EXTERNAL_STORAGE); return false; @@ -677,8 +678,8 @@ public class DecryptListFragment @Override public void onRequestPermissionsResult(int requestCode, - @NonNull String[] permissions, - @NonNull int[] grantResults) { + @NonNull String[] permissions, + @NonNull int[] grantResults) { if (requestCode != REQUEST_PERMISSION_READ_EXTERNAL_STORAGE) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); @@ -694,7 +695,7 @@ public class DecryptListFragment Iterator it = mCancelledInputUris.iterator(); while (it.hasNext()) { Uri uri = it.next(); - if ( ! ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { + if (!ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { continue; } it.remove(); @@ -712,7 +713,7 @@ public class DecryptListFragment Iterator it = mPendingInputUris.iterator(); while (it.hasNext()) { Uri uri = it.next(); - if ( ! ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { + if (!ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { continue; } it.remove(); @@ -767,7 +768,7 @@ public class DecryptListFragment { ParcelableKeyRing keyEntry = new ParcelableKeyRing(null, - KeyFormattingUtils.convertKeyIdToHex(unknownKeyId)); + KeyFormattingUtils.convertKeyIdToHex(unknownKeyId), null, null); ArrayList selectedEntries = new ArrayList<>(); selectedEntries.add(keyEntry); @@ -975,7 +976,7 @@ public class DecryptListFragment String filename; if (metadata == null) { filename = getString(R.string.filename_unknown); - } else if ( ! TextUtils.isEmpty(metadata.getFilename())) { + } else if (!TextUtils.isEmpty(metadata.getFilename())) { filename = metadata.getFilename(); } else if (ClipDescription.compareMimeTypes(metadata.getMimeType(), Constants.MIME_TYPE_KEYS)) { filename = getString(R.string.filename_keys); @@ -1227,7 +1228,7 @@ public class DecryptListFragment vSigStatusText = (TextView) itemView.findViewById(R.id.result_signature_text); vSignatureLayout = itemView.findViewById(R.id.result_signature_layout); vSignatureName = (TextView) itemView.findViewById(R.id.result_signature_name); - vSignatureMail= (TextView) itemView.findViewById(R.id.result_signature_email); + vSignatureMail = (TextView) itemView.findViewById(R.id.result_signature_email); vSignatureAction = (ViewAnimator) itemView.findViewById(R.id.result_signature_action); vFileList = (LinearLayout) itemView.findViewById(R.id.file_list); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java index 258aebadf..3de0d26d2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java @@ -125,10 +125,11 @@ public class EncryptFilesFragment } @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - if ( ! (activity instanceof EncryptActivity) ) { - throw new AssertionError(activity + " must inherit from EncryptionActivity"); + public void onAttach(Context context) { + super.onAttach(context); + + if ( ! (context instanceof EncryptActivity) ) { + throw new AssertionError(context + " must inherit from EncryptionActivity"); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java index e02184873..1939ea20a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java @@ -82,10 +82,11 @@ public class EncryptTextFragment } @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - if ( ! (activity instanceof EncryptActivity) ) { - throw new AssertionError(activity + " must inherit from EncryptionActivity"); + public void onAttach(Context context) { + super.onAttach(context); + + if ( ! (context instanceof EncryptActivity) ) { + throw new AssertionError(context + " must inherit from EncryptionActivity"); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpAboutFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpAboutFragment.java index e2be108dc..41cdf5883 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpAboutFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpAboutFragment.java @@ -28,7 +28,7 @@ import android.view.ViewGroup; import android.widget.TextView; import org.markdown4j.Markdown4jProcessor; -import org.sufficientlysecure.htmltextview.HtmlLocalImageGetter; +import org.sufficientlysecure.htmltextview.HtmlResImageGetter; import org.sufficientlysecure.htmltextview.HtmlTextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; @@ -52,7 +52,7 @@ public class HelpAboutFragment extends Fragment { try { String html = new Markdown4jProcessor().process( getActivity().getResources().openRawResource(R.raw.help_about)); - aboutTextView.setHtml(html, new HtmlLocalImageGetter(aboutTextView)); + aboutTextView.setHtml(html, new HtmlResImageGetter(aboutTextView)); } catch (IOException e) { Log.e(Constants.TAG, "IOException", e); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpMarkdownFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpMarkdownFragment.java index e0e1a4773..4f9882b73 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpMarkdownFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpMarkdownFragment.java @@ -26,7 +26,7 @@ import android.view.ViewGroup; import android.widget.ScrollView; import org.markdown4j.Markdown4jProcessor; -import org.sufficientlysecure.htmltextview.HtmlLocalImageGetter; +import org.sufficientlysecure.htmltextview.HtmlResImageGetter; import org.sufficientlysecure.htmltextview.HtmlTextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.util.Log; @@ -69,7 +69,7 @@ public class HelpMarkdownFragment extends Fragment { try { String html = new Markdown4jProcessor().process( getActivity().getResources().openRawResource(mHtmlFile)); - text.setHtml(html, new HtmlLocalImageGetter(text)); + text.setHtml(html, new HtmlResImageGetter(text)); } catch (IOException e) { Log.e(Constants.TAG, "IOException", e); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java index c741f8d88..2060dfef0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -1,18 +1,18 @@ /* - * Copyright (C) 2012-2014 Dominik Schürmann - * Copyright (C) 2011 Senecaso + * Copyright (C) 2012-2016 Dominik Schürmann * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * http://www.apache.org/licenses/LICENSE-2.0 + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; @@ -24,8 +24,6 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; -import android.view.View; -import android.view.View.OnClickListener; import android.view.ViewGroup; import org.sufficientlysecure.keychain.Constants; @@ -34,6 +32,10 @@ import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; import org.sufficientlysecure.keychain.keyimport.FacebookKeyserver; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; +import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListener; +import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysOperationCallback; +import org.sufficientlysecure.keychain.keyimport.processing.LoaderState; +import org.sufficientlysecure.keychain.operations.ImportOperation; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.base.BaseActivity; @@ -42,15 +44,14 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableFileCache; -import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; import org.sufficientlysecure.keychain.keyimport.ParcelableHkpKeyserver; import org.sufficientlysecure.keychain.util.Preferences; import java.io.IOException; import java.util.ArrayList; +import java.util.List; -public class ImportKeysActivity extends BaseActivity - implements CryptoOperationHelper.Callback { +public class ImportKeysActivity extends BaseActivity implements ImportKeysListener { public static final String ACTION_IMPORT_KEY = OpenKeychainIntents.IMPORT_KEY; public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER = OpenKeychainIntents.IMPORT_KEY_FROM_KEYSERVER; @@ -79,13 +80,8 @@ public class ImportKeysActivity extends BaseActivity public static final String TAG_FRAG_LIST = "frag_list"; public static final String TAG_FRAG_TOP = "frag_top"; - // for CryptoOperationHelper.Callback - private ParcelableHkpKeyserver mKeyserver; - private ArrayList mKeyList; - - private CryptoOperationHelper mOperationHelper; - private boolean mFreshIntent; + private CryptoOperationHelper mOpHelper; @Override protected void onCreate(Bundle savedInstanceState) { @@ -95,12 +91,6 @@ public class ImportKeysActivity extends BaseActivity mFreshIntent = true; setFullScreenDialogClose(Activity.RESULT_CANCELED, true); - findViewById(R.id.import_import).setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - importSelectedKeys(); - } - }); } @Override @@ -182,7 +172,7 @@ public class ImportKeysActivity extends BaseActivity if (query != null && query.length() > 0) { // display keyserver fragment with query - startTopCloudFragment(query, false, null); + startTopCloudFragment(query, null); // action: search immediately startListFragment(null, null, query, null); @@ -200,9 +190,6 @@ public class ImportKeysActivity extends BaseActivity if (isFingerprintValid(fingerprint)) { String query = "0x" + fingerprint; - // display keyserver fragment with query - startTopCloudFragment(query, true, null); - // action: search immediately startListFragment(null, null, query, null); } @@ -220,27 +207,35 @@ public class ImportKeysActivity extends BaseActivity Preferences.CloudSearchPrefs cloudSearchPrefs = new Preferences.CloudSearchPrefs(false, true, true, null); - // we allow our users to edit the query if they wish - startTopCloudFragment(fbUsername, false, cloudSearchPrefs); // search immediately startListFragment(null, null, fbUsername, cloudSearchPrefs); break; } case ACTION_SEARCH_KEYSERVER_FROM_URL: { - // need to process URL to get search query and keyserver authority - String query = dataUri.getQueryParameter("search"); - // if query not specified, we still allow users to search the keyserver in the link - if (query == null) { - Notify.create(this, R.string.import_url_warn_no_search_parameter, Notify.LENGTH_INDEFINITE, - Notify.Style.WARN).show(); - } - ParcelableHkpKeyserver keyserver = new ParcelableHkpKeyserver(dataUri.getAuthority()); + // get keyserver from URL + ParcelableHkpKeyserver keyserver = new ParcelableHkpKeyserver( + dataUri.getScheme() + "://" + dataUri.getAuthority()); Preferences.CloudSearchPrefs cloudSearchPrefs = new Preferences.CloudSearchPrefs( - true, true, true, keyserver); - // we allow our users to edit the query if they wish - startTopCloudFragment(query, false, cloudSearchPrefs); - // search immediately (if query is not null) - startListFragment(null, null, query, cloudSearchPrefs); + true, false, false, keyserver); + Log.d(Constants.TAG, "Using keyserver: " + keyserver); + + // process URL to get operation and query + String operation = dataUri.getQueryParameter("op"); + String query = dataUri.getQueryParameter("search"); + + // if query or operation not specified, we still allow users to search + if (query == null || operation == null) { + startTopCloudFragment(null, cloudSearchPrefs); + startListFragment(null, null, null, cloudSearchPrefs); + } else { + if (operation.equalsIgnoreCase("get")) { + // don't allow searching here, only one key! + startListFragment(null, null, query, cloudSearchPrefs); + } else { // for example: operation: index + startTopCloudFragment(query, cloudSearchPrefs); + startListFragment(null, null, query, cloudSearchPrefs); + } + } break; } case ACTION_IMPORT_KEY_FROM_FILE: @@ -251,24 +246,13 @@ public class ImportKeysActivity extends BaseActivity break; } default: { - startTopCloudFragment(null, false, null); + startTopCloudFragment(null, null); startListFragment(null, null, null, null); break; } } } - @Override - protected void onRestoreInstanceState(Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); - - // the only thing we need to take care of for restoring state is - // that the top layout is shown iff it contains a fragment - Fragment topFragment = getSupportFragmentManager().findFragmentByTag(TAG_FRAG_TOP); - boolean hasTopFragment = topFragment != null; - findViewById(R.id.import_keys_top_layout).setVisibility(hasTopFragment ? View.VISIBLE : View.GONE); - } - /** * Shows the list of keys to be imported. * If the fragment is started with non-null bytes/dataUri/serverQuery, it will immediately @@ -282,6 +266,7 @@ public class ImportKeysActivity extends BaseActivity */ private void startListFragment(byte[] bytes, Uri dataUri, String serverQuery, Preferences.CloudSearchPrefs cloudSearchPrefs) { + Fragment listFragment = ImportKeysListFragment.newInstance(bytes, dataUri, serverQuery, false, cloudSearchPrefs); @@ -291,30 +276,29 @@ public class ImportKeysActivity extends BaseActivity } private void startTopFileFragment() { - findViewById(R.id.import_keys_top_layout).setVisibility(View.VISIBLE); - Fragment importFileFragment = ImportKeysFileFragment.newInstance(); - getSupportFragmentManager().beginTransaction() - .replace(R.id.import_keys_top_container, importFileFragment, TAG_FRAG_TOP) - .commit(); + FragmentManager fM = getSupportFragmentManager(); + if (fM.findFragmentByTag(TAG_FRAG_TOP) == null) { + Fragment importFileFragment = ImportKeysFileFragment.newInstance(); + fM.beginTransaction().add(importFileFragment, TAG_FRAG_TOP).commit(); + } } /** - * loads the CloudFragment, which consists of the search bar, search button and settings icon - * visually. + * loads the CloudFragment, which enables the search bar * * @param query search query - * @param disableQueryEdit if true, user will not be able to edit the search query * @param cloudSearchPrefs keyserver authority to use for search, if null will use keyserver * specified in user preferences */ - private void startTopCloudFragment(String query, boolean disableQueryEdit, + private void startTopCloudFragment(String query, Preferences.CloudSearchPrefs cloudSearchPrefs) { - findViewById(R.id.import_keys_top_layout).setVisibility(View.VISIBLE); - Fragment importCloudFragment = ImportKeysCloudFragment.newInstance(query, disableQueryEdit, - cloudSearchPrefs); - getSupportFragmentManager().beginTransaction() - .replace(R.id.import_keys_top_container, importCloudFragment, TAG_FRAG_TOP) - .commit(); + + FragmentManager fM = getSupportFragmentManager(); + if (fM.findFragmentByTag(TAG_FRAG_TOP) == null) { + Fragment importCloudFragment = ImportKeysSearchFragment.newInstance(query, + cloudSearchPrefs); + fM.beginTransaction().add(importCloudFragment, TAG_FRAG_TOP).commit(); + } } private boolean isFingerprintValid(String fingerprint) { @@ -327,96 +311,68 @@ public class ImportKeysActivity extends BaseActivity } } - public void loadCallback(final ImportKeysListFragment.LoaderState loaderState) { - FragmentManager fragMan = getSupportFragmentManager(); - ImportKeysListFragment keyListFragment = (ImportKeysListFragment) fragMan.findFragmentByTag(TAG_FRAG_LIST); - keyListFragment.loadNew(loaderState); - } - - private void importSelectedKeys() { - - FragmentManager fragMan = getSupportFragmentManager(); - ImportKeysListFragment keyListFragment = (ImportKeysListFragment) fragMan.findFragmentByTag(TAG_FRAG_LIST); - - if (keyListFragment.getSelectedEntries().size() == 0) { - Notify.create(this, R.string.error_nothing_import_selected, Notify.Style.ERROR) - .show((ViewGroup) findViewById(R.id.import_snackbar)); - return; - } - - mOperationHelper = new CryptoOperationHelper<>( - 1, this, this, R.string.progress_importing - ); - - ImportKeysListFragment.LoaderState ls = keyListFragment.getLoaderState(); - if (ls instanceof ImportKeysListFragment.BytesLoaderState) { - Log.d(Constants.TAG, "importKeys started"); - - // get DATA from selected key entries - IteratorWithSize selectedEntries = keyListFragment.getSelectedData(); - - // instead of giving the entries by Intent extra, cache them into a - // file to prevent Java Binder problems on heavy imports - // read FileImportCache for more info. - try { - // We parcel this iteratively into a file - anything we can - // display here, we should be able to import. - ParcelableFileCache cache = - new ParcelableFileCache<>(this, "key_import.pcl"); - cache.writeCache(selectedEntries); - - mKeyList = null; - mKeyserver = null; - mOperationHelper.cryptoOperation(); - - } catch (IOException e) { - Log.e(Constants.TAG, "Problem writing cache file", e); - Notify.create(this, "Problem writing cache file!", Notify.Style.ERROR) - .show((ViewGroup) findViewById(R.id.import_snackbar)); - } - } else if (ls instanceof ImportKeysListFragment.CloudLoaderState) { - ImportKeysListFragment.CloudLoaderState sls = - (ImportKeysListFragment.CloudLoaderState) ls; - - // get selected key entries - ArrayList keys = new ArrayList<>(); - { - // change the format into ParcelableKeyRing - ArrayList entries = keyListFragment.getSelectedEntries(); - for (ImportKeysListEntry entry : entries) { - keys.add(new ParcelableKeyRing(entry.getFingerprintHex(), - entry.getKeyIdHex(), entry.getKeybaseName(), entry.getFbUsername())); - } - } - - mKeyList = keys; - mKeyserver = sls.mCloudPrefs.keyserver; - mOperationHelper.cryptoOperation(); - - } - } - @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (mOperationHelper != null && - mOperationHelper.handleActivityResult(requestCode, resultCode, data)) { + if (mOpHelper != null && + mOpHelper.handleActivityResult(requestCode, resultCode, data)) { return; } super.onActivityResult(requestCode, resultCode, data); } - /** - * Defines how the result of this activity is returned. - * Is overwritten in RemoteImportKeysActivity - */ - protected void handleResult(ImportKeyResult result) { + @Override + public void onBackPressed() { + FragmentManager fM = getSupportFragmentManager(); + ImportKeysListFragment listFragment = + (ImportKeysListFragment) fM.findFragmentByTag(TAG_FRAG_LIST); + + if ((listFragment == null) || listFragment.onBackPressed()) { + super.onBackPressed(); + } + } + + @Override + public void loadKeys(LoaderState loaderState) { + FragmentManager fM = getSupportFragmentManager(); + ((ImportKeysListFragment) fM.findFragmentByTag(TAG_FRAG_LIST)).loadState(loaderState); + } + + @Override + public void importKeys(List entries) { + List keyRings = new ArrayList<>(); + for (ImportKeysListEntry e : entries) { + keyRings.add(e.getParcelableKeyRing()); + } + // instead of giving the entries by Intent extra, cache them into a + // file to prevent Java Binder problems on heavy imports + // read FileImportCache for more info. + try { + // We parcel this iteratively into a file - anything we can + // display here, we should be able to import. + ParcelableFileCache cache = + new ParcelableFileCache<>(this, ImportOperation.CACHE_FILE_NAME); + cache.writeCache(entries.size(), keyRings.iterator()); + } catch (IOException e) { + Log.e(Constants.TAG, "Problem writing cache file", e); + Notify.create(this, "Problem writing cache file!", Notify.Style.ERROR).show(); + return; + } + + ImportKeyringParcel inputParcel = new ImportKeyringParcel(null, null); + ImportKeysOperationCallback callback = new ImportKeysOperationCallback(this, inputParcel); + mOpHelper = new CryptoOperationHelper<>(1, this, callback, R.string.progress_importing); + mOpHelper.cryptoOperation(); + } + + @Override + public void handleResult(ImportKeyResult result) { String intentAction = getIntent().getAction(); - if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(intentAction) - || ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(intentAction)) { + if (ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(intentAction) + || ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(intentAction)) { Intent intent = new Intent(); intent.putExtra(ImportKeyResult.EXTRA_RESULT, result); - setResult(RESULT_OK, intent); + setResult(Activity.RESULT_OK, intent); finish(); } else if (result.isOkNew() || result.isOkUpdated()) { // User has successfully imported a key, hide first time dialog @@ -424,38 +380,12 @@ public class ImportKeysActivity extends BaseActivity // Close activities opened for importing keys and go to the list of keys Intent intent = new Intent(this, MainActivity.class); + intent.putExtra(ImportKeyResult.EXTRA_RESULT, result); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); startActivity(intent); } else { - result.createNotify(ImportKeysActivity.this) - .show((ViewGroup) findViewById(R.id.import_snackbar)); + result.createNotify(this).show(); } } - // methods from CryptoOperationHelper.Callback - - @Override - public ImportKeyringParcel createOperationInput() { - return new ImportKeyringParcel(mKeyList, mKeyserver); - } - - @Override - public void onCryptoOperationSuccess(ImportKeyResult result) { - handleResult(result); - } - - @Override - public void onCryptoOperationCancelled() { - // do nothing - } - - @Override - public void onCryptoOperationError(ImportKeyResult result) { - handleResult(result); - } - - @Override - public boolean onCryptoSetProgress(String msg, int progress, int max) { - return false; - } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysCloudFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysCloudFragment.java deleted file mode 100644 index fb0217cda..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysCloudFragment.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright (C) 2013-2014 Dominik Schürmann - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.preference.PreferenceActivity; -import android.support.v4.app.Fragment; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; -import android.widget.ArrayAdapter; -import android.widget.AutoCompleteTextView; -import android.widget.TextView; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.util.ContactHelper; -import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.Preferences; - -import java.util.List; - -/** - * Consists of the search bar, search button, and search settings button - */ -public class ImportKeysCloudFragment extends Fragment { - public static final String ARG_QUERY = "query"; - public static final String ARG_DISABLE_QUERY_EDIT = "disable_query_edit"; - public static final String ARG_CLOUD_SEARCH_PREFS = "cloud_search_prefs"; - - private ImportKeysActivity mImportActivity; - - private AutoCompleteTextView mQueryEditText; - - /** - * Creates new instance of this fragment - * - * @param query query to search for - * @param disableQueryEdit if true, user cannot edit query - * @param cloudSearchPrefs search parameters to use. If null will retrieve from user's - * preferences. - */ - public static ImportKeysCloudFragment newInstance(String query, boolean disableQueryEdit, - Preferences.CloudSearchPrefs - cloudSearchPrefs) { - ImportKeysCloudFragment frag = new ImportKeysCloudFragment(); - - Bundle args = new Bundle(); - args.putString(ARG_QUERY, query); - args.putBoolean(ARG_DISABLE_QUERY_EDIT, disableQueryEdit); - args.putParcelable(ARG_CLOUD_SEARCH_PREFS, cloudSearchPrefs); - - frag.setArguments(args); - - return frag; - } - - /** - * Inflate the layout for this fragment - */ - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.import_keys_cloud_fragment, container, false); - - mQueryEditText = (AutoCompleteTextView) view.findViewById(R.id.cloud_import_server_query); - - ContactHelper contactHelper = new ContactHelper(getActivity()); - List namesAndEmails = contactHelper.getContactNames(); - namesAndEmails.addAll(contactHelper.getContactMails()); - mQueryEditText.setThreshold(3); - mQueryEditText.setAdapter( - new ArrayAdapter<> - (getActivity(), android.R.layout.simple_spinner_dropdown_item, - namesAndEmails - ) - ); - - View searchButton = view.findViewById(R.id.cloud_import_server_search); - searchButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - search(mQueryEditText.getText().toString()); - } - }); - - mQueryEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { - @Override - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - if (actionId == EditorInfo.IME_ACTION_SEARCH) { - search(mQueryEditText.getText().toString()); - - // Don't return true to let the keyboard close itself after pressing search - return false; - } - return false; - } - }); - - View configButton = view.findViewById(R.id.cloud_import_server_config_button); - configButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - Intent intent = new Intent(mImportActivity, SettingsActivity.class); - intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, SettingsActivity.CloudSearchPrefsFragment.class.getName()); - startActivity(intent); - } - }); - - return view; - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - // set displayed values - if (getArguments() != null) { - String query = getArguments().getString(ARG_QUERY); - if (query != null) { - mQueryEditText.setText(query, TextView.BufferType.EDITABLE); - - Log.d(Constants.TAG, "query: " + query); - } else { - // open keyboard - mQueryEditText.requestFocus(); - toggleKeyboard(true); - } - - if (getArguments().getBoolean(ARG_DISABLE_QUERY_EDIT, false)) { - mQueryEditText.setEnabled(false); - } - } - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - - mImportActivity = (ImportKeysActivity) activity; - } - - private void search(String query) { - Preferences.CloudSearchPrefs cloudSearchPrefs - = getArguments().getParcelable(ARG_CLOUD_SEARCH_PREFS); - - // no explicit search preferences passed - if (cloudSearchPrefs == null) { - cloudSearchPrefs = Preferences.getPreferences(getActivity()).getCloudSearchPrefs(); - } - - mImportActivity.loadCallback( - new ImportKeysListFragment.CloudLoaderState(query, cloudSearchPrefs)); - toggleKeyboard(false); - } - - private void toggleKeyboard(boolean show) { - if (getActivity() == null) { - return; - } - InputMethodManager inputManager = (InputMethodManager) getActivity() - .getSystemService(Context.INPUT_METHOD_SERVICE); - - // check if no view has focus - View v = getActivity().getCurrentFocus(); - if (v == null) { - return; - } - - if (show) { - inputManager.showSoftInput(v, InputMethodManager.SHOW_IMPLICIT); - } else { - inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0); - } - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java index 133cf299f..9bf106788 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java @@ -17,46 +17,42 @@ package org.sufficientlysecure.keychain.ui; -import android.Manifest; import android.app.Activity; -import android.content.ContentResolver; +import android.content.Context; import android.content.Intent; -import android.content.pm.PackageManager; import android.net.Uri; -import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.Fragment; -import android.support.v4.content.ContextCompat; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; +import org.sufficientlysecure.keychain.keyimport.processing.BytesLoaderState; +import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListener; import org.sufficientlysecure.keychain.pgp.PgpHelper; -import org.sufficientlysecure.keychain.ui.ImportKeysListFragment.BytesLoaderState; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; +import org.sufficientlysecure.keychain.ui.util.PermissionsUtil; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.Log; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; public class ImportKeysFileFragment extends Fragment { - private ImportKeysActivity mImportActivity; - private View mBrowse; - private View mClipboardButton; + + private Activity mActivity; + private ImportKeysListener mCallback; private Uri mCurrentUri; private static final int REQUEST_CODE_FILE = 0x00007003; - private static final int REQUEST_PERMISSION_READ_EXTERNAL_STORAGE = 12; /** * Creates new instance of this fragment @@ -70,52 +66,60 @@ public class ImportKeysFileFragment extends Fragment { return frag; } - /** - * Inflate the layout for this fragment - */ @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.import_keys_file_fragment, container, false); + public View onCreateView(LayoutInflater i, ViewGroup c, Bundle savedInstanceState) { + setHasOptionsMenu(true); + return null; + } - mBrowse = view.findViewById(R.id.import_keys_file_browse); + @Override + public void onAttach(Context context) { + super.onAttach(context); - mBrowse.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { + try { + mCallback = (ImportKeysListener) context; + } catch (ClassCastException e) { + throw new ClassCastException(context.toString() + + " must implement ImportKeysListener"); + } + + mActivity = (Activity) context; + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.import_keys_file_fragment, menu); + + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int itemId = item.getItemId(); + switch (itemId) { + case R.id.menu_import_keys_file_open: // open .asc or .gpg files // setting it to text/plain prevents Cyanogenmod's file manager from selecting asc // or gpg types! FileHelper.openDocument(ImportKeysFileFragment.this, Uri.fromFile(Constants.Path.APP_DIR), "*/*", false, REQUEST_CODE_FILE); - } - }); - - mClipboardButton = view.findViewById(R.id.import_clipboard_button); - mClipboardButton.setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(View v) { + return true; + case R.id.menu_import_keys_file_paste: CharSequence clipboardText = ClipboardReflection.getClipboardText(getActivity()); String sendText = ""; if (clipboardText != null) { sendText = clipboardText.toString(); sendText = PgpHelper.getPgpKeyContent(sendText); if (sendText == null) { - Notify.create(mImportActivity, R.string.error_bad_data, Style.ERROR).show(); - return; + Notify.create(mActivity, R.string.error_bad_data, Style.ERROR).show(); + } else { + mCallback.loadKeys(new BytesLoaderState(sendText.getBytes(), null)); } - mImportActivity.loadCallback(new BytesLoaderState(sendText.getBytes(), null)); } - } - }); + return true; + } - return view; - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - - mImportActivity = (ImportKeysActivity) activity; + return super.onOptionsItemSelected(item); } @Override @@ -125,90 +129,49 @@ public class ImportKeysFileFragment extends Fragment { if (resultCode == Activity.RESULT_OK && data != null && data.getData() != null) { mCurrentUri = data.getData(); - if (checkAndRequestReadPermission(mCurrentUri)) { + if (PermissionsUtil.checkAndRequestReadPermission(this, mCurrentUri)) { startImportingKeys(); } } break; } - default: super.onActivityResult(requestCode, resultCode, data); - - break; } } private void startImportingKeys() { boolean isEncrypted; try { - isEncrypted = FileHelper.isEncryptedFile(mImportActivity, mCurrentUri); + isEncrypted = FileHelper.isEncryptedFile(mActivity, mCurrentUri); } catch (IOException e) { Log.e(Constants.TAG, "Error opening file", e); - Notify.create(mImportActivity, R.string.error_bad_data, Style.ERROR).show(); + Notify.create(mActivity, R.string.error_bad_data, Style.ERROR).show(); return; } if (isEncrypted) { - Intent intent = new Intent(mImportActivity, DecryptActivity.class); + Intent intent = new Intent(mActivity, DecryptActivity.class); intent.setAction(Intent.ACTION_VIEW); intent.setData(mCurrentUri); startActivity(intent); } else { - mImportActivity.loadCallback(new BytesLoaderState(null, mCurrentUri)); + mCallback.loadKeys(new BytesLoaderState(null, mCurrentUri)); } } - /** - * Request READ_EXTERNAL_STORAGE permission on Android >= 6.0 to read content from "file" Uris. - *

- * This method returns true on Android < 6, or if permission is already granted. It - * requests the permission and returns false otherwise. - *

- * see https://commonsware.com/blog/2015/10/07/runtime-permissions-files-action-send.html - */ - private boolean checkAndRequestReadPermission(final Uri uri) { - if (!ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { - return true; - } - - // Additional check due to https://commonsware.com/blog/2015/11/09/you-cannot-hold-nonexistent-permissions.html - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - return true; - } - - if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE) - == PackageManager.PERMISSION_GRANTED) { - return true; - } - - requestPermissions( - new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, - REQUEST_PERMISSION_READ_EXTERNAL_STORAGE); - - return false; - } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - if (requestCode != REQUEST_PERMISSION_READ_EXTERNAL_STORAGE) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - return; - } - - boolean permissionWasGranted = grantResults.length > 0 - && grantResults[0] == PackageManager.PERMISSION_GRANTED; - - if (permissionWasGranted) { + if (PermissionsUtil.checkReadPermissionResult(mActivity, requestCode, grantResults)) { startImportingKeys(); } else { - Toast.makeText(getActivity(), R.string.error_denied_storage_permission, Toast.LENGTH_LONG).show(); - getActivity().setResult(Activity.RESULT_CANCELED); - getActivity().finish(); + mActivity.setResult(Activity.RESULT_CANCELED); + mActivity.finish(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java index 4d4219f56..2f6f2b030 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java @@ -18,47 +18,45 @@ package org.sufficientlysecure.keychain.ui; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import android.Manifest; import android.app.Activity; -import android.content.ContentResolver; -import android.content.pm.PackageManager; +import android.content.Context; +import android.databinding.DataBindingUtil; import android.net.Uri; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.support.annotation.NonNull; -import android.support.v4.app.ListFragment; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentActivity; import android.support.v4.app.LoaderManager; -import android.support.v4.content.ContextCompat; import android.support.v4.content.Loader; -import android.support.v4.util.LongSparseArray; -import android.view.MotionEvent; +import android.support.v7.widget.LinearLayoutManager; +import android.view.LayoutInflater; import android.view.View; -import android.view.View.OnTouchListener; -import android.widget.ListView; -import android.widget.Toast; +import android.view.View.OnClickListener; +import android.view.ViewGroup; -import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.databinding.ImportKeysListFragmentBinding; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; -import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; +import org.sufficientlysecure.keychain.keyimport.processing.AsyncTaskResultWrapper; +import org.sufficientlysecure.keychain.keyimport.processing.BytesLoaderState; +import org.sufficientlysecure.keychain.keyimport.processing.CloudLoaderState; +import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListCloudLoader; +import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListLoader; +import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListener; +import org.sufficientlysecure.keychain.keyimport.processing.LoaderState; import org.sufficientlysecure.keychain.operations.results.GetKeyResult; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; -import org.sufficientlysecure.keychain.ui.adapter.AsyncTaskResultWrapper; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter; -import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListCloudLoader; -import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListLoader; -import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; +import org.sufficientlysecure.keychain.ui.util.PermissionsUtil; import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.Preferences.CloudSearchPrefs; import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; -public class ImportKeysListFragment extends ListFragment implements +import java.util.ArrayList; + +public class ImportKeysListFragment extends Fragment implements LoaderManager.LoaderCallbacks>> { private static final String ARG_DATA_URI = "uri"; @@ -67,74 +65,25 @@ public class ImportKeysListFragment extends ListFragment implements public static final String ARG_NON_INTERACTIVE = "non_interactive"; public static final String ARG_CLOUD_SEARCH_PREFS = "cloud_search_prefs"; - private static final int REQUEST_PERMISSION_READ_EXTERNAL_STORAGE = 12; + private FragmentActivity mActivity; + private ImportKeysListener mListener; - private Activity mActivity; - private ImportKeysAdapter mAdapter; + private ImportKeysListFragmentBinding mBinding; private ParcelableProxy mParcelableProxy; + private ImportKeysAdapter mAdapter; private LoaderState mLoaderState; + public static final int STATUS_FIRST = 0; + public static final int STATUS_LOADING = 1; + public static final int STATUS_LOADED = 2; + public static final int STATUS_EMPTY = 3; + private static final int LOADER_ID_BYTES = 0; private static final int LOADER_ID_CLOUD = 1; - private LongSparseArray mCachedKeyData; - private boolean mNonInteractive; - private boolean mShowingOrbotDialog; - public LoaderState getLoaderState() { - return mLoaderState; - } - - public List getData() { - return mAdapter.getData(); - } - - /** - * Returns an Iterator (with size) of the selected data items. - * This iterator is sort of a tradeoff, it's slightly more complex than an - * ArrayList would have been, but we save some memory by just returning - * relevant elements on demand. - */ - public IteratorWithSize getSelectedData() { - final ArrayList entries = getSelectedEntries(); - final Iterator it = entries.iterator(); - return new IteratorWithSize() { - - @Override - public int getSize() { - return entries.size(); - } - - @Override - public boolean hasNext() { - return it.hasNext(); - } - - @Override - public ParcelableKeyRing next() { - // throws NoSuchElementException if it doesn't exist, but that's not our problem - return mCachedKeyData.get(it.next().hashCode()); - } - - @Override - public void remove() { - it.remove(); - } - }; - } - - public ArrayList getSelectedEntries() { - if (mAdapter != null) { - return mAdapter.getSelectedEntries(); - } else { - Log.e(Constants.TAG, "Adapter not initialized, returning empty list"); - return new ArrayList<>(); - } - - } - /** * Creates an interactive ImportKeyListFragment which reads keyrings from bytes, or file specified * by dataUri, or searches a keyserver for serverQuery, if parameter is not null, in that order @@ -148,7 +97,8 @@ public class ImportKeysListFragment extends ListFragment implements * @return fragment with arguments set based on passed parameters */ public static ImportKeysListFragment newInstance(byte[] bytes, Uri dataUri, String serverQuery, - Preferences.CloudSearchPrefs cloudSearchPrefs) { + CloudSearchPrefs cloudSearchPrefs) { + return newInstance(bytes, dataUri, serverQuery, false, cloudSearchPrefs); } @@ -168,8 +118,7 @@ public class ImportKeysListFragment extends ListFragment implements Uri dataUri, String serverQuery, boolean nonInteractive, - Preferences.CloudSearchPrefs cloudSearchPrefs) { - ImportKeysListFragment frag = new ImportKeysListFragment(); + CloudSearchPrefs cloudSearchPrefs) { Bundle args = new Bundle(); args.putByteArray(ARG_BYTES, bytes); @@ -178,115 +127,68 @@ public class ImportKeysListFragment extends ListFragment implements args.putBoolean(ARG_NON_INTERACTIVE, nonInteractive); args.putParcelable(ARG_CLOUD_SEARCH_PREFS, cloudSearchPrefs); + ImportKeysListFragment frag = new ImportKeysListFragment(); frag.setArguments(args); - return frag; } - static public class LoaderState { - } - - static public class BytesLoaderState extends LoaderState { - public byte[] mKeyBytes; - public Uri mDataUri; - - BytesLoaderState(byte[] keyBytes, Uri dataUri) { - mKeyBytes = keyBytes; - mDataUri = dataUri; - } - } - - static public class CloudLoaderState extends LoaderState { - Preferences.CloudSearchPrefs mCloudPrefs; - String mServerQuery; - - CloudLoaderState(String serverQuery, Preferences.CloudSearchPrefs cloudPrefs) { - mServerQuery = serverQuery; - mCloudPrefs = cloudPrefs; - } - } - - /** - * Define Adapter and Loader on create of Activity - */ @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) { + mBinding = DataBindingUtil.inflate(inflater, R.layout.import_keys_list_fragment, container, false); + mBinding.setStatus(STATUS_FIRST); + View view = mBinding.getRoot(); mActivity = getActivity(); - // Give some text to display if there is no data. - setEmptyText(mActivity.getString(R.string.error_nothing_import)); - - // Create an empty adapter we will use to display the loaded data. - mAdapter = new ImportKeysAdapter(mActivity); - setListAdapter(mAdapter); - Bundle args = getArguments(); Uri dataUri = args.getParcelable(ARG_DATA_URI); byte[] bytes = args.getByteArray(ARG_BYTES); String query = args.getString(ARG_SERVER_QUERY); - mNonInteractive = args.getBoolean(ARG_NON_INTERACTIVE, false); + boolean nonInteractive = args.getBoolean(ARG_NON_INTERACTIVE, false); + mBinding.basic.setNonInteractive(nonInteractive); - getListView().setOnTouchListener(new OnTouchListener() { + // Create an empty adapter we will use to display the loaded data. + mAdapter = new ImportKeysAdapter(mActivity, mListener, nonInteractive); + mBinding.recyclerView.setAdapter(mAdapter); + mBinding.recyclerView.setLayoutManager(new LinearLayoutManager(mActivity)); + + if (dataUri != null || bytes != null) { + loadState(new BytesLoaderState(bytes, dataUri)); + } else if (query != null) { + CloudSearchPrefs cloudSearchPrefs = args.getParcelable(ARG_CLOUD_SEARCH_PREFS); + if (cloudSearchPrefs == null) { + cloudSearchPrefs = Preferences.getPreferences(mActivity).getCloudSearchPrefs(); + } + loadState(new CloudLoaderState(query, cloudSearchPrefs)); + } + + // mBinding.basic is only used for file import + mBinding.basic.importKeys.setOnClickListener(new OnClickListener() { @Override - public boolean onTouch(View v, MotionEvent event) { - if (!mAdapter.isEmpty()) { - mActivity.onTouchEvent(event); - } - return false; + public void onClick(View view) { + mListener.importKeys(mAdapter.getEntries()); + } + }); + mBinding.basic.listKeys.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + mBinding.setAdvanced(true); } }); - getListView().setFastScrollEnabled(true); - - if (dataUri != null || bytes != null) { - mLoaderState = new BytesLoaderState(bytes, dataUri); - } else if (query != null) { - Preferences.CloudSearchPrefs cloudSearchPrefs - = args.getParcelable(ARG_CLOUD_SEARCH_PREFS); - if (cloudSearchPrefs == null) { - cloudSearchPrefs = Preferences.getPreferences(getActivity()).getCloudSearchPrefs(); - } - - mLoaderState = new CloudLoaderState(query, cloudSearchPrefs); - } - - if (dataUri != null && ! checkAndRequestReadPermission(dataUri)) { - return; - } - - restartLoaders(); + return view; } - /** - * Request READ_EXTERNAL_STORAGE permission on Android >= 6.0 to read content from "file" Uris. - * - * This method returns true on Android < 6, or if permission is already granted. It - * requests the permission and returns false otherwise. - * - * see https://commonsware.com/blog/2015/10/07/runtime-permissions-files-action-send.html - */ - private boolean checkAndRequestReadPermission(final Uri uri) { - if ( ! ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { - return true; + @Override + public void onAttach(Context context) { + super.onAttach(context); + + try { + mListener = (ImportKeysListener) context; + } catch (ClassCastException e) { + throw new ClassCastException(context.toString() + + " must implement ImportKeysListener"); } - - // Additional check due to https://commonsware.com/blog/2015/11/09/you-cannot-hold-nonexistent-permissions.html - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - return true; - } - - if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE) - == PackageManager.PERMISSION_GRANTED) { - return true; - } - - requestPermissions( - new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, - REQUEST_PERMISSION_READ_EXTERNAL_STORAGE); - - return false; } @Override @@ -294,140 +196,99 @@ public class ImportKeysListFragment extends ListFragment implements @NonNull String[] permissions, @NonNull int[] grantResults) { - if (requestCode != REQUEST_PERMISSION_READ_EXTERNAL_STORAGE) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - return; - } - - boolean permissionWasGranted = grantResults.length > 0 - && grantResults[0] == PackageManager.PERMISSION_GRANTED; - - if (permissionWasGranted) { - // permission granted -> load key + if (PermissionsUtil.checkReadPermissionResult(mActivity, requestCode, grantResults)) { restartLoaders(); } else { - Toast.makeText(getActivity(), R.string.error_denied_storage_permission, Toast.LENGTH_LONG).show(); - getActivity().setResult(Activity.RESULT_CANCELED); - getActivity().finish(); + mActivity.setResult(Activity.RESULT_CANCELED); + mActivity.finish(); } } - @Override - public void onListItemClick(ListView l, View v, int position, long id) { - super.onListItemClick(l, v, position, id); - - if (mNonInteractive) { - return; + /** + * User may want to go back to single card view if he's now in full key list + * Check if we are in full key list and if this import operation supports basic mode + * + * @return true if activity's back pressed can be performed + */ + public boolean onBackPressed() { + boolean advanced = mBinding.getAdvanced(); + if (advanced && mLoaderState.isBasicModeSupported()) { + mBinding.setAdvanced(false); + return false; } - - // Select checkbox! - // Update underlying data and notify adapter of change. The adapter will - // update the view automatically. - - ImportKeysListEntry entry = mAdapter.getItem(position); - entry.setSelected(!entry.isSelected()); - mAdapter.notifyDataSetChanged(); + return true; } - public void loadNew(LoaderState loaderState) { + public void loadState(LoaderState loaderState) { mLoaderState = loaderState; if (mLoaderState instanceof BytesLoaderState) { BytesLoaderState ls = (BytesLoaderState) mLoaderState; - if ( ls.mDataUri != null && ! checkAndRequestReadPermission(ls.mDataUri)) { + if (ls.mDataUri != null && + !PermissionsUtil.checkAndRequestReadPermission(this, ls.mDataUri)) { return; } } + mBinding.setAdvanced(!mLoaderState.isBasicModeSupported()); restartLoaders(); } - public void destroyLoader() { - if (getLoaderManager().getLoader(LOADER_ID_BYTES) != null) { - getLoaderManager().destroyLoader(LOADER_ID_BYTES); - } - if (getLoaderManager().getLoader(LOADER_ID_CLOUD) != null) { - getLoaderManager().destroyLoader(LOADER_ID_CLOUD); - } - if (getView() != null) { - setListShown(true); - } - } - private void restartLoaders() { + LoaderManager loaderManager = getLoaderManager(); + if (mLoaderState instanceof BytesLoaderState) { - // Start out with a progress indicator. - setListShown(false); - - getLoaderManager().restartLoader(LOADER_ID_BYTES, null, this); + loaderManager.restartLoader(LOADER_ID_BYTES, null, this); } else if (mLoaderState instanceof CloudLoaderState) { - // Start out with a progress indicator. - setListShown(false); - - getLoaderManager().restartLoader(LOADER_ID_CLOUD, null, this); + loaderManager.restartLoader(LOADER_ID_CLOUD, null, this); } } @Override - public Loader>> - onCreateLoader(int id, Bundle args) { + public Loader>> onCreateLoader( + int id, Bundle args) { + + Loader>> loader = null; switch (id) { case LOADER_ID_BYTES: { - return new ImportKeysListLoader(mActivity, (BytesLoaderState) mLoaderState); + loader = new ImportKeysListLoader(mActivity, (BytesLoaderState) mLoaderState); + break; } case LOADER_ID_CLOUD: { - CloudLoaderState ls = (CloudLoaderState) mLoaderState; - return new ImportKeysListCloudLoader(getActivity(), ls.mServerQuery, ls.mCloudPrefs, + loader = new ImportKeysListCloudLoader(mActivity, (CloudLoaderState) mLoaderState, mParcelableProxy); + break; } - - default: - return null; } + + if (loader != null) { + mBinding.setStatus(STATUS_LOADING); + } + + return loader; } @Override - public void onLoadFinished(Loader>> loader, - AsyncTaskResultWrapper> data) { - // Swap the new cursor in. (The framework will take care of closing the - // old cursor once we return.) + public void onLoadFinished( + Loader>> loader, + AsyncTaskResultWrapper> data) { - Log.d(Constants.TAG, "data: " + data.getResult()); - - // swap in the real data! mAdapter.setData(data.getResult()); - mAdapter.notifyDataSetChanged(); + int size = mAdapter.getItemCount(); - setListAdapter(mAdapter); - - // The list should now be shown. - if (isResumed()) { - setListShown(true); - } else { - setListShownNoAnimation(true); - } - - // free old cached key data - mCachedKeyData = null; + mBinding.setNumber(size); + mBinding.setStatus(size > 0 ? STATUS_LOADED : STATUS_EMPTY); GetKeyResult getKeyResult = (GetKeyResult) data.getOperationResult(); switch (loader.getId()) { case LOADER_ID_BYTES: - - if (getKeyResult.success()) { - // No error - mCachedKeyData = ((ImportKeysListLoader) loader).getParcelableRings(); - } else { - getKeyResult.createNotify(getActivity()).show(); + if (!getKeyResult.success()) { + getKeyResult.createNotify(mActivity).show(); } break; - case LOADER_ID_CLOUD: - - if (getKeyResult.success()) { - // No error - } else if (getKeyResult.isPending()) { + if (getKeyResult.isPending()) { if (getKeyResult.getRequiredInputParcel().mType == RequiredInputParcel.RequiredInputType.ENABLE_ORBOT) { if (mShowingOrbotDialog) { @@ -461,8 +322,7 @@ public class ImportKeysListFragment extends ListFragment implements } }; - if (OrbotHelper.putOrbotInRequiredState(dialogActions, - getActivity())) { + if (OrbotHelper.putOrbotInRequiredState(dialogActions, mActivity)) { // looks like we didn't have to show the // dialog after all mShowingOrbotDialog = false; @@ -473,30 +333,18 @@ public class ImportKeysListFragment extends ListFragment implements new Handler().post(showOrbotDialog); mShowingOrbotDialog = true; } - } else { - getKeyResult.createNotify(getActivity()).show(); + } else if (!getKeyResult.success()) { + getKeyResult.createNotify(mActivity).show(); } break; - - default: - break; } } @Override - public void onLoaderReset(Loader>> loader) { - switch (loader.getId()) { - case LOADER_ID_BYTES: - // Clear the data in the adapter. - mAdapter.clear(); - break; - case LOADER_ID_CLOUD: - // Clear the data in the adapter. - mAdapter.clear(); - break; - default: - break; - } + public void onLoaderReset( + Loader>> loader) { + + mAdapter.clearData(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java index 4c5151fe9..fa91be2e4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java @@ -198,7 +198,7 @@ public class ImportKeysProxyActivity extends FragmentActivity } public void importKeys(String fingerprint) { - ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null); + ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null, null); ArrayList selectedEntries = new ArrayList<>(); selectedEntries.add(keyEntry); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysSearchFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysSearchFragment.java new file mode 100644 index 000000000..d77de6fa9 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysSearchFragment.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2013-2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.ui; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.database.MatrixCursor; +import android.os.Bundle; +import android.preference.PreferenceActivity; +import android.provider.BaseColumns; +import android.support.v4.app.Fragment; +import android.support.v4.view.MenuItemCompat; +import android.support.v4.view.MenuItemCompat.OnActionExpandListener; +import android.support.v4.widget.CursorAdapter; +import android.support.v4.widget.SimpleCursorAdapter; +import android.support.v7.widget.SearchView; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.keyimport.processing.CloudLoaderState; +import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListener; +import org.sufficientlysecure.keychain.util.ContactHelper; +import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.Preferences.CloudSearchPrefs; + +import java.util.ArrayList; +import java.util.List; + +import static android.support.v7.widget.SearchView.OnQueryTextListener; +import static android.support.v7.widget.SearchView.OnSuggestionListener; + +/** + * Consists of the search bar, search button, and search settings button + */ +public class ImportKeysSearchFragment extends Fragment { + + public static final String ARG_QUERY = "query"; + public static final String ARG_CLOUD_SEARCH_PREFS = "cloud_search_prefs"; + + private static final String CURSOR_SUGGESTION = "suggestion"; + + private Activity mActivity; + private ImportKeysListener mCallback; + + private List mNamesAndEmails; + private SimpleCursorAdapter mSearchAdapter; + + private List mCurrentSuggestions = new ArrayList<>(); + + /** + * Creates new instance of this fragment + * + * @param query query to search for + * @param cloudSearchPrefs search parameters to use. If null will retrieve from user's + * preferences. + */ + public static ImportKeysSearchFragment newInstance(String query, + CloudSearchPrefs cloudSearchPrefs) { + + ImportKeysSearchFragment frag = new ImportKeysSearchFragment(); + + Bundle args = new Bundle(); + args.putString(ARG_QUERY, query); + args.putParcelable(ARG_CLOUD_SEARCH_PREFS, cloudSearchPrefs); + + frag.setArguments(args); + + return frag; + } + + @Override + public View onCreateView(LayoutInflater i, ViewGroup c, Bundle savedInstanceState) { + ContactHelper contactHelper = new ContactHelper(mActivity); + mNamesAndEmails = contactHelper.getContactNames(); + mNamesAndEmails.addAll(contactHelper.getContactMails()); + + mSearchAdapter = new SimpleCursorAdapter(mActivity, + R.layout.import_keys_cloud_suggestions_item, null, new String[]{CURSOR_SUGGESTION}, + new int[]{android.R.id.text1}, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER); + + setHasOptionsMenu(true); + + // no view, just search view + return null; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + + try { + mCallback = (ImportKeysListener) context; + } catch (ClassCastException e) { + throw new ClassCastException(context.toString() + + " must implement ImportKeysListener"); + } + + mActivity = (Activity) context; + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.import_keys_cloud_fragment, menu); + + MenuItem searchItem = menu.findItem(R.id.menu_import_keys_cloud_search); + final SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem); + searchView.setSuggestionsAdapter(mSearchAdapter); + + searchView.setOnSuggestionListener(new OnSuggestionListener() { + @Override + public boolean onSuggestionSelect(int position) { + searchView.setQuery(mCurrentSuggestions.get(position), true); + return true; + } + + @Override + public boolean onSuggestionClick(int position) { + searchView.setQuery(mCurrentSuggestions.get(position), true); + return true; + } + }); + + searchView.setOnQueryTextListener(new OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String query) { + searchView.clearFocus(); + search(searchView.getQuery().toString().trim()); + return true; + } + + @Override + public boolean onQueryTextChange(String newText) { + updateAdapter(newText); + return false; + } + }); + + MenuItemCompat.setOnActionExpandListener(searchItem, new OnActionExpandListener() { + @Override + public boolean onMenuItemActionExpand(MenuItem item) { + return true; + } + + @Override + public boolean onMenuItemActionCollapse(MenuItem item) { + mActivity.finish(); + return true; + } + }); + + searchItem.expandActionView(); + + String query = getArguments().getString(ARG_QUERY); + if (query != null) { + searchView.setQuery(query, false); + } + + super.onCreateOptionsMenu(menu, inflater); + } + + private void updateAdapter(String query) { + mCurrentSuggestions.clear(); + MatrixCursor c = new MatrixCursor(new String[]{BaseColumns._ID, CURSOR_SUGGESTION}); + for (int i = 0; i < mNamesAndEmails.size(); i++) { + String s = mNamesAndEmails.get(i); + if (s.toLowerCase().startsWith(query.toLowerCase())) { + mCurrentSuggestions.add(s); + c.addRow(new Object[]{i, s}); + } + + } + mSearchAdapter.changeCursor(c); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int itemId = item.getItemId(); + switch (itemId) { + case R.id.menu_import_keys_cloud_settings: + Intent intent = new Intent(mActivity, SettingsActivity.class); + intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, + SettingsActivity.CloudSearchPrefsFragment.class.getName()); + startActivity(intent); + return true; + } + + return super.onOptionsItemSelected(item); + } + + private void search(String query) { + CloudSearchPrefs cloudSearchPrefs + = getArguments().getParcelable(ARG_CLOUD_SEARCH_PREFS); + + // no explicit search preferences passed + if (cloudSearchPrefs == null) { + cloudSearchPrefs = Preferences.getPreferences(getActivity()).getCloudSearchPrefs(); + } + + mCallback.loadKeys(new CloudLoaderState(query, cloudSearchPrefs)); + toggleKeyboard(false); + } + + private void toggleKeyboard(boolean show) { + if (getActivity() == null) { + return; + } + InputMethodManager inputManager = (InputMethodManager) getActivity() + .getSystemService(Context.INPUT_METHOD_SERVICE); + + // check if no view has focus + View v = getActivity().getCurrentFocus(); + if (v == null) { + return; + } + + if (show) { + inputManager.showSoftInput(v, InputMethodManager.SHOW_IMPLICIT); + } else { + inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0); + } + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index 6b88f4e12..e2ccd041b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -54,6 +54,7 @@ import android.widget.ViewAnimator; import com.getbase.floatingactionbutton.FloatingActionButton; import com.getbase.floatingactionbutton.FloatingActionsMenu; + import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; @@ -78,6 +79,7 @@ import org.sufficientlysecure.keychain.util.FabContainer; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.keyimport.ParcelableHkpKeyserver; import org.sufficientlysecure.keychain.util.Preferences; + import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter; import se.emilsjolander.stickylistheaders.StickyListHeadersListView; @@ -245,7 +247,7 @@ public class KeyListFragment extends LoaderFragment @Override public void onItemCheckedStateChanged(ActionMode mode, int position, long id, - boolean checked) { + boolean checked) { if (checked) { mAdapter.setNewSelection(position, true); } else { @@ -334,7 +336,7 @@ public class KeyListFragment extends LoaderFragment headerCursor.addRow(row); Cursor dataCursor = data; - data = new MergeCursor(new Cursor[] { + data = new MergeCursor(new Cursor[]{ headerCursor, dataCursor }); } @@ -576,7 +578,7 @@ public class KeyListFragment extends LoaderFragment while (cursor.moveToNext()) { byte[] blob = cursor.getBlob(0);//fingerprint column is 0 String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob); - ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null); + ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null, null); keyList.add(keyEntry); } mKeyList = keyList; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java index 30612857c..410e511fa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java @@ -30,6 +30,7 @@ import android.widget.NumberPicker; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; +import org.sufficientlysecure.keychain.operations.ImportOperation; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.provider.KeychainContract; @@ -142,7 +143,7 @@ public class SafeSlingerActivity extends BaseActivity // We parcel this iteratively into a file - anything we can // display here, we should be able to import. ParcelableFileCache cache = - new ParcelableFileCache<>(this, "key_import.pcl"); + new ParcelableFileCache<>(this, ImportOperation.CACHE_FILE_NAME); cache.writeCache(it.size(), it.iterator()); mOperationHelper = diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 7961ab4b6..96f620a4c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -19,11 +19,6 @@ package org.sufficientlysecure.keychain.ui; -import java.io.IOException; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; - import android.animation.ArgbEvaluator; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; @@ -89,10 +84,10 @@ import org.sufficientlysecure.keychain.ui.ViewKeyFragment.PostponeType; import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; +import org.sufficientlysecure.keychain.ui.util.ContentDescriptionHint; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; -import org.sufficientlysecure.keychain.ui.util.ContentDescriptionHint; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener; import org.sufficientlysecure.keychain.ui.util.Notify.Style; @@ -104,6 +99,11 @@ import org.sufficientlysecure.keychain.keyimport.ParcelableHkpKeyserver; import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Preferences; +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; + public class ViewKeyActivity extends BaseSecurityTokenActivity implements LoaderManager.LoaderCallbacks, @@ -115,7 +115,9 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements @Retention(RetentionPolicy.SOURCE) @IntDef({REQUEST_QR_FINGERPRINT, REQUEST_BACKUP, REQUEST_CERTIFY, REQUEST_DELETE}) - private @interface RequestType {} + private @interface RequestType { + } + static final int REQUEST_QR_FINGERPRINT = 1; static final int REQUEST_BACKUP = 2; static final int REQUEST_CERTIFY = 3; @@ -565,7 +567,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements private void startBackupActivity() { Intent intent = new Intent(this, BackupActivity.class); - intent.putExtra(BackupActivity.EXTRA_MASTER_KEY_IDS, new long[] { mMasterKeyId }); + intent.putExtra(BackupActivity.EXTRA_MASTER_KEY_IDS, new long[]{mMasterKeyId}); intent.putExtra(BackupActivity.EXTRA_SECRET, true); startActivity(intent); } @@ -722,7 +724,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements manager.beginTransaction() .addToBackStack("security_token") .replace(R.id.view_key_fragment, frag) - // if this is called while the activity wasn't resumed, just forget it happened + // if this is called while the activity wasn't resumed, just forget it happened .commitAllowingStateLoss(); } }); @@ -1105,7 +1107,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements KeychainContract.Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB); String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob); - ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null); + ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null, null); ArrayList entries = new ArrayList<>(); entries.add(keyEntry); mKeyList = entries; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java index 0be64629b..110ecbe9e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java @@ -225,7 +225,8 @@ public class ViewKeyAdvActivity extends BaseActivity implements // get key id from MASTER_KEY_ID long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID); - getSupportActionBar().setSubtitle(KeyFormattingUtils.beautifyKeyIdWithPrefix(this, masterKeyId)); + String formattedKeyId = KeyFormattingUtils.beautifyKeyIdWithPrefix(masterKeyId); + getSupportActionBar().setSubtitle(formattedKeyId); mHasSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; boolean isRevoked = data.getInt(INDEX_IS_REVOKED) > 0; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvCertsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvCertsFragment.java index c39881e5b..d540ba0cb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvCertsFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvCertsFragment.java @@ -237,7 +237,8 @@ public class ViewKeyAdvCertsFragment extends LoaderFragment implements TextView wSignerName = (TextView) view.findViewById(R.id.signerName); TextView wSignStatus = (TextView) view.findViewById(R.id.signStatus); - String signerKeyId = KeyFormattingUtils.beautifyKeyIdWithPrefix(getActivity(), cursor.getLong(mIndexSignerKeyId)); + String signerKeyId = KeyFormattingUtils.beautifyKeyIdWithPrefix( + cursor.getLong(mIndexSignerKeyId)); OpenPgpUtils.UserId userId = KeyRing.splitUserId(cursor.getString(mIndexSignerUserId)); if (userId.name != null) { wSignerName.setText(userId.name); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java index a6d311134..6e692fde5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java @@ -17,261 +17,295 @@ package org.sufficientlysecure.keychain.ui.adapter; -import android.annotation.TargetApi; -import android.app.Activity; -import android.content.Context; -import android.graphics.Color; -import android.os.Build; +import android.content.Intent; +import android.databinding.DataBindingUtil; +import android.support.v4.app.FragmentActivity; +import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; +import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.CheckBox; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; -import org.openintents.openpgp.util.OpenPgpUtils; +import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.databinding.ImportKeysListItemBinding; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; +import org.sufficientlysecure.keychain.keyimport.ParcelableHkpKeyserver; +import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; +import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListener; +import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysOperationCallback; +import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysResultListener; import org.sufficientlysecure.keychain.operations.ImportOperation; +import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; +import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing; import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.ui.util.FormattingUtils; -import org.sufficientlysecure.keychain.ui.util.Highlighter; +import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.ImportKeyringParcel; +import org.sufficientlysecure.keychain.ui.ViewKeyActivity; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.ParcelableFileCache; +import java.io.IOException; import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; +import java.util.Date; import java.util.List; -import java.util.Map; -public class ImportKeysAdapter extends ArrayAdapter { - protected LayoutInflater mInflater; - protected Activity mActivity; +public class ImportKeysAdapter extends RecyclerView.Adapter implements ImportKeysResultListener { - protected List mData; + private FragmentActivity mActivity; + private ImportKeysResultListener mListener; + private boolean mNonInteractive; - static class ViewHolder { - public TextView mainUserId; - public TextView mainUserIdRest; - public TextView keyId; - public TextView fingerprint; - public TextView algorithm; - public ImageView status; - public View userIdsDivider; - public LinearLayout userIdsList; - public CheckBox checkBox; - } + private List mData; + private KeyState[] mKeyStates; + private int mCurrent; + + private ProviderHelper mProviderHelper; + + public ImportKeysAdapter(FragmentActivity activity, ImportKeysListener listener, + boolean nonInteractive) { - public ImportKeysAdapter(Activity activity) { - super(activity, -1); mActivity = activity; - mInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mListener = listener; + mNonInteractive = nonInteractive; + + mProviderHelper = new ProviderHelper(activity); } - @TargetApi(Build.VERSION_CODES.HONEYCOMB) public void setData(List data) { + mData = data; - clear(); - if (data != null) { - this.mData = data; + mKeyStates = new KeyState[data.size()]; + for (int i = 0; i < mKeyStates.length; i++) { + mKeyStates[i] = new KeyState(); - // add data to extended ArrayAdapter - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - addAll(data); - } else { - for (ImportKeysListEntry entry : data) { - add(entry); + ImportKeysListEntry entry = mData.get(i); + long keyId = KeyFormattingUtils.convertKeyIdHexToKeyId(entry.getKeyIdHex()); + try { + KeyRing keyRing; + if (entry.isSecretKey()) { + keyRing = mProviderHelper.getCanonicalizedSecretKeyRing(keyId); + } else { + keyRing = mProviderHelper.getCachedPublicKeyRing(keyId); } + mKeyStates[i].mAlreadyPresent = true; + mKeyStates[i].mVerified = keyRing.getVerified() > 0; + } catch (ProviderHelper.NotFoundException | PgpKeyNotFoundException ignored) { } } + + // If there is only one key, get it automatically + if (mData.size() == 1) { + mCurrent = 0; + getKeyWithProgress(0, mData.get(0), true); + } + + notifyDataSetChanged(); } - public List getData() { - return mData; + public void clearData() { + mData = null; + mKeyStates = null; + notifyDataSetChanged(); } - /** This method returns a list of all selected entries, with public keys sorted + /** + * This method returns a list of all selected entries, with public keys sorted * before secret keys, see ImportOperation for specifics. + * * @see ImportOperation */ - public ArrayList getSelectedEntries() { + public List getEntries() { ArrayList result = new ArrayList<>(); ArrayList secrets = new ArrayList<>(); if (mData == null) { return result; } for (ImportKeysListEntry entry : mData) { - if (entry.isSelected()) { - // add this entry to either the secret or the public list - (entry.isSecretKey() ? secrets : result).add(entry); - } + // add this entry to either the secret or the public list + (entry.isSecretKey() ? secrets : result).add(entry); } // add secret keys at the end of the list result.addAll(secrets); return result; } - @Override - public boolean hasStableIds() { - return true; + public class ViewHolder extends RecyclerView.ViewHolder { + public ImportKeysListItemBinding b; + + public ViewHolder(View view) { + super(view); + b = DataBindingUtil.bind(view); + b.setNonInteractive(mNonInteractive); + } } - public View getView(int position, View convertView, ViewGroup parent) { - ImportKeysListEntry entry = mData.get(position); - Highlighter highlighter = new Highlighter(mActivity, entry.getQuery()); - ViewHolder holder; - if (convertView == null) { - holder = new ViewHolder(); - convertView = mInflater.inflate(R.layout.import_keys_list_item, null); - holder.mainUserId = (TextView) convertView.findViewById(R.id.import_item_user_id); - holder.mainUserIdRest = (TextView) convertView.findViewById(R.id.import_item_user_id_email); - holder.keyId = (TextView) convertView.findViewById(R.id.import_item_key_id); - holder.fingerprint = (TextView) convertView.findViewById(R.id.import_item_fingerprint); - holder.algorithm = (TextView) convertView.findViewById(R.id.import_item_algorithm); - holder.status = (ImageView) convertView.findViewById(R.id.import_item_status); - holder.userIdsDivider = convertView.findViewById(R.id.import_item_status_divider); - holder.userIdsList = (LinearLayout) convertView.findViewById(R.id.import_item_user_ids_list); - holder.checkBox = (CheckBox) convertView.findViewById(R.id.import_item_selected); - convertView.setTag(holder); - } else { - holder = (ViewHolder) convertView.getTag(); - } + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + LayoutInflater inflater = LayoutInflater.from(mActivity); + return new ViewHolder(inflater.inflate(R.layout.import_keys_list_item, parent, false)); + } - // main user id - String userId = entry.getUserIds().get(0); - OpenPgpUtils.UserId userIdSplit = KeyRing.splitUserId(userId); + @Override + public void onBindViewHolder(ViewHolder holder, final int position) { + final ImportKeysListItemBinding b = holder.b; + final ImportKeysListEntry entry = mData.get(position); + b.setEntry(entry); - // name - if (userIdSplit.name != null) { - // show red user id if it is a secret key - if (entry.isSecretKey()) { - holder.mainUserId.setText(mActivity.getString(R.string.secret_key) - + " " + userIdSplit.name); - } else { - holder.mainUserId.setText(highlighter.highlight(userIdSplit.name)); - } - } else { - holder.mainUserId.setText(R.string.user_id_no_name); - } + final KeyState keyState = mKeyStates[position]; - // email - if (userIdSplit.email != null) { - holder.mainUserIdRest.setVisibility(View.VISIBLE); - holder.mainUserIdRest.setText(highlighter.highlight(userIdSplit.email)); - } else { - holder.mainUserIdRest.setVisibility(View.GONE); - } + b.card.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (!keyState.mDownloaded) { + mCurrent = position; - holder.keyId.setText(KeyFormattingUtils.beautifyKeyIdWithPrefix(getContext(), entry.getKeyIdHex())); - - // don't show full fingerprint on key import - holder.fingerprint.setVisibility(View.GONE); - - if (entry.getAlgorithm() != null) { - holder.algorithm.setText(entry.getAlgorithm()); - holder.algorithm.setVisibility(View.VISIBLE); - } else { - holder.algorithm.setVisibility(View.GONE); - } - - if (entry.isRevoked()) { - KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, State.REVOKED, R.color.key_flag_gray); - } else if (entry.isExpired()) { - KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, State.EXPIRED, R.color.key_flag_gray); - } - - if (entry.isRevoked() || entry.isExpired()) { - holder.status.setVisibility(View.VISIBLE); - - // no more space for algorithm display - holder.algorithm.setVisibility(View.GONE); - - holder.mainUserId.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray)); - holder.mainUserIdRest.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray)); - holder.keyId.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray)); - } else { - holder.status.setVisibility(View.GONE); - holder.algorithm.setVisibility(View.VISIBLE); - - if (entry.isSecretKey()) { - holder.mainUserId.setTextColor(Color.RED); - } else { - holder.mainUserId.setTextColor(FormattingUtils.getColorFromAttr(mActivity, R.attr.colorText)); - } - - holder.mainUserIdRest.setTextColor(FormattingUtils.getColorFromAttr(mActivity, R.attr.colorText)); - holder.keyId.setTextColor(FormattingUtils.getColorFromAttr(mActivity, R.attr.colorText)); - } - - if (entry.getUserIds().size() == 1) { - holder.userIdsList.setVisibility(View.GONE); - holder.userIdsDivider.setVisibility(View.GONE); - } else { - holder.userIdsList.setVisibility(View.VISIBLE); - holder.userIdsDivider.setVisibility(View.VISIBLE); - - // destroyLoader view from holder - holder.userIdsList.removeAllViews(); - - // we want conventional gpg UserIDs first, then Keybase ”proofs” - HashMap> mergedUserIds = entry.getMergedUserIds(); - ArrayList>> sortedIds = new ArrayList>>(mergedUserIds.entrySet()); - java.util.Collections.sort(sortedIds, new java.util.Comparator>>() { - @Override - public int compare(Map.Entry> entry1, Map.Entry> entry2) { - - // sort keybase UserIds after non-Keybase - boolean e1IsKeybase = entry1.getKey().contains(":"); - boolean e2IsKeybase = entry2.getKey().contains(":"); - if (e1IsKeybase != e2IsKeybase) { - return (e1IsKeybase) ? 1 : -1; - } - return entry1.getKey().compareTo(entry2.getKey()); - } - }); - - for (Map.Entry> pair : sortedIds) { - String cUserId = pair.getKey(); - HashSet cEmails = pair.getValue(); - - TextView uidView = (TextView) mInflater.inflate( - R.layout.import_keys_list_entry_user_id, null); - uidView.setText(highlighter.highlight(cUserId)); - uidView.setPadding(0, 0, FormattingUtils.dpToPx(getContext(), 8), 0); - - if (entry.isRevoked() || entry.isExpired()) { - uidView.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray)); + getKeyWithProgress(position, entry, true); } else { - uidView.setTextColor(FormattingUtils.getColorFromAttr(getContext(), R.attr.colorText)); - } - - holder.userIdsList.addView(uidView); - - for (String email : cEmails) { - TextView emailView = (TextView) mInflater.inflate( - R.layout.import_keys_list_entry_user_id, null); - emailView.setPadding( - FormattingUtils.dpToPx(getContext(), 16), 0, - FormattingUtils.dpToPx(getContext(), 8), 0); - emailView.setText(highlighter.highlight(email)); - - if (entry.isRevoked() || entry.isExpired()) { - emailView.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray)); - } else { - emailView.setTextColor(FormattingUtils.getColorFromAttr(getContext(), R.attr.colorText)); - } - - holder.userIdsList.addView(emailView); + changeShowed(position, !keyState.mShowed); } } + }); + + b.extra.importKey.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + getKeyWithProgress(position, entry, false); + } + }); + + b.extra.showKey.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + long keyId = KeyFormattingUtils.convertKeyIdHexToKeyId(entry.getKeyIdHex()); + Intent intent = new Intent(mActivity, ViewKeyActivity.class); + intent.setData(KeyRings.buildGenericKeyRingUri(keyId)); + mActivity.startActivity(intent); + } + }); + + b.extraContainer.setVisibility(keyState.mShowed ? View.VISIBLE : View.GONE); + + b.progress.setVisibility(keyState.mProgress ? View.VISIBLE : View.GONE); + } + + @Override + public int getItemCount() { + return mData != null ? mData.size() : 0; + } + + private void getKeyWithProgress(int position, ImportKeysListEntry entry, boolean skipSave) { + changeProgress(position, true); + getKey(entry, skipSave); + } + + private void getKey(ImportKeysListEntry entry, boolean skipSave) { + ImportKeyringParcel inputParcel = prepareKeyOperation(entry, skipSave); + ImportKeysResultListener listener = skipSave ? this : mListener; + ImportKeysOperationCallback cb = new ImportKeysOperationCallback(listener, inputParcel); + CryptoOperationHelper opHelper = new CryptoOperationHelper<>(1, mActivity, cb, null); + opHelper.cryptoOperation(); + } + + private ImportKeyringParcel prepareKeyOperation(ImportKeysListEntry entry, boolean skipSave) { + ArrayList keysList = null; + ParcelableHkpKeyserver keyserver = null; + + ParcelableKeyRing keyRing = entry.getParcelableKeyRing(); + if (keyRing.mBytes != null) { + // instead of giving the entries by Intent extra, cache them into a + // file to prevent Java Binder problems on heavy imports + // read FileImportCache for more info. + try { + // We parcel this iteratively into a file - anything we can + // display here, we should be able to import. + ParcelableFileCache cache = + new ParcelableFileCache<>(mActivity, ImportOperation.CACHE_FILE_NAME); + cache.writeCache(keyRing); + } catch (IOException e) { + Log.e(Constants.TAG, "Problem writing cache file", e); + Notify.create(mActivity, "Problem writing cache file!", Notify.Style.ERROR).show(); + } + } else { + keysList = new ArrayList<>(); + keysList.add(keyRing); + keyserver = entry.getKeyserver(); } - holder.checkBox.setChecked(entry.isSelected()); + return new ImportKeyringParcel(keysList, keyserver, skipSave); + } - return convertView; + @Override + public void handleResult(ImportKeyResult result) { + boolean resultStatus = result.success(); + Log.e(Constants.TAG, "getKey result: " + resultStatus); + if (resultStatus) { + ArrayList canKeyRings = result.mCanonicalizedKeyRings; + if (canKeyRings.size() == 1) { + CanonicalizedKeyRing keyRing = canKeyRings.get(0); + Log.e(Constants.TAG, "Key ID: " + keyRing.getMasterKeyId() + + "| isRev: " + keyRing.isRevoked() + "| isExp: " + keyRing.isExpired()); + + ImportKeysListEntry entry = mData.get(mCurrent); + entry.setUpdated(result.isOkUpdated()); + + mergeEntryWithKey(entry, keyRing); + + mKeyStates[mCurrent].mDownloaded = true; + changeShowed(mCurrent, true); + } else { + throw new RuntimeException("getKey retrieved more than one key (" + + canKeyRings.size() + ")"); + } + } else { + result.createNotify(mActivity).show(); + } + + changeProgress(mCurrent, false); + } + + private void mergeEntryWithKey(ImportKeysListEntry entry, CanonicalizedKeyRing keyRing) { + entry.setRevoked(keyRing.isRevoked()); + entry.setExpired(keyRing.isExpired()); + + Date expectedDate = entry.getDate(); + Date creationDate = keyRing.getCreationDate(); + if (expectedDate == null) { + entry.setDate(creationDate); + } else if (!expectedDate.equals(creationDate)) { + throw new AssertionError("Creation date doesn't match the expected one"); + } + entry.setKeyId(keyRing.getMasterKeyId()); + + ArrayList realUserIdsPlusKeybase = keyRing.getUnorderedUserIds(); + realUserIdsPlusKeybase.addAll(entry.getKeybaseUserIds()); + entry.setUserIds(realUserIdsPlusKeybase); + } + + private class KeyState { + boolean mAlreadyPresent = false; + boolean mVerified = false; + + boolean mProgress = false; + boolean mDownloaded = false; + boolean mShowed = false; + } + + private void changeShowed(int position, boolean showed) { + KeyState keyState = mKeyStates[position]; + keyState.mShowed = showed; + notifyItemChanged(position); + } + + private void changeProgress(int position, boolean progress) { + KeyState keyState = mKeyStates[position]; + keyState.mProgress = progress; + notifyItemChanged(position); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/bindings/ImportKeysBindings.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/bindings/ImportKeysBindings.java new file mode 100644 index 000000000..415fc28ea --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/bindings/ImportKeysBindings.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2016 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.ui.bindings; + +import android.content.Context; +import android.content.res.Resources; +import android.databinding.BindingAdapter; +import android.graphics.Color; +import android.text.format.DateFormat; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.util.Highlighter; + +import java.util.Date; + +public class ImportKeysBindings { + + @BindingAdapter({"keyUserId", "keySecret", "keyRevokedOrExpired", "query"}) + public static void setUserId(TextView textView, CharSequence userId, boolean secret, + boolean revokedOrExpired, String query) { + + Context context = textView.getContext(); + Resources resources = context.getResources(); + + if (userId == null) { + userId = resources.getString(R.string.user_id_no_name); + } + + if (secret) { + userId = resources.getString(R.string.secret_key) + " " + userId; + } else { + Highlighter highlighter = ImportKeysBindingsUtils.getHighlighter(context, query); + userId = highlighter.highlight(userId); + } + textView.setText(userId); + textView.setTextColor(ImportKeysBindingsUtils.getColor(context, revokedOrExpired)); + + if (secret) { + textView.setTextColor(Color.RED); + } + } + + @BindingAdapter({"keyUserEmail", "keyRevokedOrExpired", "query"}) + public static void setUserEmail(TextView textView, CharSequence userEmail, + boolean revokedOrExpired, String query) { + + Context context = textView.getContext(); + + if (userEmail == null) { + userEmail = ""; + } + + Highlighter highlighter = ImportKeysBindingsUtils.getHighlighter(context, query); + textView.setText(highlighter.highlight(userEmail)); + textView.setTextColor(ImportKeysBindingsUtils.getColor(context, revokedOrExpired)); + } + + @BindingAdapter({"keyCreation", "keyRevokedOrExpired"}) + public static void setCreation(TextView textView, Date creationDate, boolean revokedOrExpired) { + Context context = textView.getContext(); + + String text = ""; + if (creationDate != null) { + text = DateFormat.getDateFormat(context).format(creationDate); + } + + textView.setText(text); + textView.setTextColor(ImportKeysBindingsUtils.getColor(context, revokedOrExpired)); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/bindings/ImportKeysBindingsUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/bindings/ImportKeysBindingsUtils.java new file mode 100644 index 000000000..24f533ebf --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/bindings/ImportKeysBindingsUtils.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2016 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.ui.bindings; + +import android.content.Context; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; +import org.sufficientlysecure.keychain.ui.util.Highlighter; +import org.sufficientlysecure.keychain.util.LruCache; + +public class ImportKeysBindingsUtils { + + private static LruCache highlighterCache = new LruCache<>(1); + + public static Highlighter getHighlighter(Context context, String query) { + Highlighter highlighter = highlighterCache.get(query); + if (highlighter == null) { + highlighter = new Highlighter(context, query); + highlighterCache.put(query, highlighter); + } + + return highlighter; + } + + public static int getColor(Context context, boolean revokedOrExpired) { + if (revokedOrExpired) { + return context.getResources().getColor(R.color.key_flag_gray); + } else { + return FormattingUtils.getColorFromAttr(context, R.attr.colorText); + } + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/bindings/ImportKeysExtraBindings.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/bindings/ImportKeysExtraBindings.java new file mode 100644 index 000000000..9c473f5d1 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/bindings/ImportKeysExtraBindings.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2016 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.ui.bindings; + +import android.content.Context; +import android.content.res.Resources; +import android.databinding.BindingAdapter; +import android.view.LayoutInflater; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; +import org.sufficientlysecure.keychain.ui.util.Highlighter; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Map; + +public class ImportKeysExtraBindings { + + @BindingAdapter({"keyRevoked", "keyExpired"}) + public static void setStatus(ImageView imageView, boolean revoked, boolean expired) { + Context context = imageView.getContext(); + + if (revoked) { + KeyFormattingUtils.setStatusImage(context, imageView, null, + KeyFormattingUtils.State.REVOKED, R.color.key_flag_gray); + } else if (expired) { + KeyFormattingUtils.setStatusImage(context, imageView, null, + KeyFormattingUtils.State.EXPIRED, R.color.key_flag_gray); + } + } + + @BindingAdapter({"keyId"}) + public static void setKeyId(TextView textView, String keyId) { + Context context = textView.getContext(); + String text; + if (keyId != null) { + text = KeyFormattingUtils.beautifyKeyId(keyId); + } else { + Resources resources = context.getResources(); + text = resources.getString(R.string.unknown); + } + textView.setText(text); + } + + @BindingAdapter({"keyUserIds", "query"}) + public static void setUserIds(LinearLayout linearLayout, ArrayList userIds, String query) { + + linearLayout.removeAllViews(); + + if (userIds != null) { + Context context = linearLayout.getContext(); + Highlighter highlighter = ImportKeysBindingsUtils.getHighlighter(context, query); + + ArrayList>> uIds = userIds; + for (Map.Entry> pair : uIds) { + String name = pair.getKey(); + HashSet emails = pair.getValue(); + + LayoutInflater inflater = LayoutInflater.from(context); + + TextView uidView = (TextView) inflater.inflate( + R.layout.import_keys_list_entry_user_id, null); + uidView.setText(highlighter.highlight(name)); + uidView.setPadding(0, 0, FormattingUtils.dpToPx(context, 8), 0); + uidView.setTextColor(FormattingUtils.getColorFromAttr(context, R.attr.colorText)); + linearLayout.addView(uidView); + + for (String email : emails) { + TextView emailView = (TextView) inflater.inflate( + R.layout.import_keys_list_entry_user_id, null); + emailView.setPadding( + FormattingUtils.dpToPx(context, 16), 0, + FormattingUtils.dpToPx(context, 8), 0); + emailView.setText(highlighter.highlight(email)); + emailView.setTextColor(FormattingUtils.getColorFromAttr(context, R.attr.colorText)); + linearLayout.addView(emailView); + } + } + } + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Highlighter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Highlighter.java index ccaa9d408..2ccf1856f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Highlighter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Highlighter.java @@ -18,7 +18,7 @@ public class Highlighter { mQuery = query; } - public Spannable highlight(String text) { + public Spannable highlight(CharSequence text) { Spannable highlight = Spannable.Factory.getInstance().newSpannable(text); if (mQuery == null) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java index 64a960d7b..18fe9c1a0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java @@ -30,13 +30,13 @@ import android.widget.ImageView; import android.widget.TextView; import android.widget.ViewAnimator; -import org.openintents.openpgp.OpenPgpDecryptionResult; -import org.openintents.openpgp.OpenPgpSignatureResult; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.nist.NISTNamedCurves; import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves; import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.bouncycastle.util.encoders.Hex; +import org.openintents.openpgp.OpenPgpDecryptionResult; +import org.openintents.openpgp.OpenPgpSignatureResult; import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; @@ -46,6 +46,7 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve; import org.sufficientlysecure.keychain.util.Log; +import java.math.BigInteger; import java.nio.ByteBuffer; import java.security.DigestException; import java.security.MessageDigest; @@ -99,7 +100,7 @@ public class KeyFormattingUtils { default: { if (context != null) { - algorithmStr = context.getResources().getString(R.string.unknown_algorithm); + algorithmStr = context.getResources().getString(R.string.unknown); } else { algorithmStr = "unknown"; } @@ -154,7 +155,7 @@ public class KeyFormattingUtils { default: { if (context != null) { - algorithmStr = context.getResources().getString(R.string.unknown_algorithm); + algorithmStr = context.getResources().getString(R.string.unknown); } else { algorithmStr = "unknown"; } @@ -189,7 +190,7 @@ public class KeyFormattingUtils { */ } if (context != null) { - return context.getResources().getString(R.string.unknown_algorithm); + return context.getResources().getString(R.string.unknown); } else { return "unknown"; } @@ -208,7 +209,7 @@ public class KeyFormattingUtils { return name; } if (context != null) { - return context.getResources().getString(R.string.unknown_algorithm); + return context.getResources().getString(R.string.unknown); } else { return "unknown"; } @@ -272,6 +273,10 @@ public class KeyFormattingUtils { return hexString; } + public static long convertKeyIdHexToKeyId(String hex) { + return new BigInteger(hex.substring(2), 16).longValue(); + } + public static long convertFingerprintToKeyId(byte[] fingerprint) { return ByteBuffer.wrap(fingerprint, 12, 8).getLong(); } @@ -312,12 +317,12 @@ public class KeyFormattingUtils { return beautifyKeyId(convertKeyIdToHex(keyId)); } - public static String beautifyKeyIdWithPrefix(Context context, String idHex) { + public static String beautifyKeyIdWithPrefix(String idHex) { return "Key ID: " + beautifyKeyId(idHex); } - public static String beautifyKeyIdWithPrefix(Context context, long keyId) { - return beautifyKeyIdWithPrefix(context, convertKeyIdToHex(keyId)); + public static String beautifyKeyIdWithPrefix(long keyId) { + return beautifyKeyIdWithPrefix(convertKeyIdToHex(keyId)); } public static SpannableStringBuilder colorizeFingerprint(String fingerprint) { @@ -434,14 +439,19 @@ public class KeyFormattingUtils { public interface StatusHolder { ImageView getEncryptionStatusIcon(); + TextView getEncryptionStatusText(); ImageView getSignatureStatusIcon(); + TextView getSignatureStatusText(); View getSignatureLayout(); + TextView getSignatureUserName(); + TextView getSignatureUserEmail(); + ViewAnimator getSignatureAction(); boolean hasEncrypt(); @@ -450,7 +460,7 @@ public class KeyFormattingUtils { @SuppressWarnings("deprecation") // context.getDrawable is api lvl 21, need to use deprecated public static void setStatus(Resources resources, StatusHolder holder, DecryptVerifyResult result, - boolean processingkeyLookup) { + boolean processingkeyLookup) { if (holder.hasEncrypt()) { OpenPgpDecryptionResult decryptionResult = result.getDecryptionResult(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/PermissionsUtil.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/PermissionsUtil.java new file mode 100644 index 000000000..e2e965779 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/PermissionsUtil.java @@ -0,0 +1,89 @@ +package org.sufficientlysecure.keychain.ui.util; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.ContentResolver; +import android.content.Context; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Build; +import android.support.v4.app.Fragment; +import android.support.v4.content.ContextCompat; +import android.widget.Toast; + +import org.sufficientlysecure.keychain.R; + + +public class PermissionsUtil { + + private static final int PERMISSION_READ_EXTERNAL_STORAGE = 1; + + /** + * Request READ_EXTERNAL_STORAGE permission on Android >= 6.0 to read content from "file" Uris. + *

+ * This method returns true on Android < 6, or if permission is already granted. It + * requests the permission and returns false otherwise. + *

+ * see https://commonsware.com/blog/2015/10/07/runtime-permissions-files-action-send.html + */ + @SuppressLint("NewApi") // Api level is checked in checkReadPermission + public static boolean checkAndRequestReadPermission(Activity activity, Uri uri) { + boolean result = checkReadPermission(activity, uri); + if (!result) { + activity.requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, + PERMISSION_READ_EXTERNAL_STORAGE); + } + return result; + } + + public static boolean checkAndRequestReadPermission(Fragment fragment, Uri uri) { + boolean result = checkReadPermission(fragment.getContext(), uri); + if (!result) { + fragment.requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, + PERMISSION_READ_EXTERNAL_STORAGE); + } + return result; + } + + private static boolean checkReadPermission(Context context, Uri uri) { + if (!ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { + return true; + } + + // Additional check due to: + // https://commonsware.com/blog/2015/11/09/you-cannot-hold-nonexistent-permissions.html + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return true; + } + + if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) + == PackageManager.PERMISSION_GRANTED) { + return true; + } + + return false; + } + + public static boolean checkReadPermissionResult(Context context, + int requestCode, + int[] grantResults) { + + if (requestCode != PERMISSION_READ_EXTERNAL_STORAGE) { + return false; + } + + boolean permissionWasGranted = grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED; + + if (permissionWasGranted) { + return true; + } else { + Toast.makeText(context, R.string.error_denied_storage_permission, Toast.LENGTH_LONG) + .show(); + + return false; + } + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java index 0d00b4937..94fae4fdc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java @@ -57,7 +57,8 @@ public class EmailKeyHelper { // Put them in a list and import ArrayList keys = new ArrayList<>(entries.size()); for (ImportKeysListEntry entry : entries) { - keys.add(new ParcelableKeyRing(entry.getFingerprintHex(), entry.getKeyIdHex())); + keys.add(new ParcelableKeyRing(entry.getFingerprintHex(), entry.getKeyIdHex(), null, + null)); } mKeyList = keys; mKeyserver = keyserver; @@ -97,7 +98,7 @@ public class EmailKeyHelper { Set keys = new HashSet<>(); try { for (ImportKeysListEntry key : keyServer.search(mail, proxy)) { - if (key.isRevoked() || key.isExpired()) continue; + if (key.isRevokedOrExpired()) continue; for (String userId : key.getUserIds()) { if (userId.toLowerCase().contains(mail.toLowerCase(Locale.ENGLISH))) { keys.add(key); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/IteratorWithSize.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/IteratorWithSize.java new file mode 100644 index 000000000..96cf5a47e --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/IteratorWithSize.java @@ -0,0 +1,17 @@ +package org.sufficientlysecure.keychain.util; + +import java.util.Iterator; + +/** + * An extended iterator interface, which knows the total number of its entries beforehand. + */ +public interface IteratorWithSize extends Iterator { + + /** + * Returns the total number of entries in this iterator. + * + * @return the number of entries in this iterator. + */ + int getSize(); + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/LruCache.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/LruCache.java new file mode 100644 index 000000000..3034a2440 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/LruCache.java @@ -0,0 +1,22 @@ +package org.sufficientlysecure.keychain.util; + +import java.util.LinkedHashMap; +import java.util.Map; + + +// Source: http://stackoverflow.com/a/1953516 +public class LruCache extends LinkedHashMap { + + private final int maxEntries; + + public LruCache(final int maxEntries) { + super(maxEntries + 1, 1.0f, true); + this.maxEntries = maxEntries; + } + + @Override + protected boolean removeEldestEntry(final Map.Entry eldest) { + return super.size() > maxEntries; + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpClientFactory.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpClientFactory.java index ea2ae8368..c4044aa25 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpClientFactory.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpClientFactory.java @@ -46,8 +46,8 @@ public class OkHttpClientFactory { .build(); } - public static OkHttpClient getClientPinnedIfAvailable(URL url, Proxy proxy) throws IOException, - TlsHelper.TlsHelperException { + public static OkHttpClient getClientPinnedIfAvailable(URL url, Proxy proxy) + throws IOException, TlsHelper.TlsHelperException { OkHttpClient.Builder builder = new OkHttpClient.Builder(); // don't follow any redirects for keyservers, as discussed in the security audit diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java index 8d3eb6963..467e2493a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java @@ -36,7 +36,7 @@ public class OkHttpKeybaseClient implements KeybaseUrlConnectionClient { @Override public Response getUrlResponse(URL url, Proxy proxy, boolean isKeybase) throws IOException { - OkHttpClient client = null; + OkHttpClient client; try { if (proxy != null) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableFileCache.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableFileCache.java index eabbf83b8..d16b2c9c9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableFileCache.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableFileCache.java @@ -52,12 +52,40 @@ public class ParcelableFileCache { mFilename = filename; } - public void writeCache(IteratorWithSize it) throws IOException { - writeCache(it.getSize(), it); + public void writeCache(int numEntries, Iterator it) throws IOException { + DataOutputStream oos = getOutputStream(); + + try { + oos.writeInt(numEntries); + while (it.hasNext()) { + writeParcelable(it.next(), oos); + } + } finally { + oos.close(); + } } - public void writeCache(int numEntries, Iterator it) throws IOException { + public void writeCache(E obj) throws IOException { + DataOutputStream oos = getOutputStream(); + try { + oos.writeInt(1); + writeParcelable(obj, oos); + } finally { + oos.close(); + } + } + + private void writeParcelable(E obj, DataOutputStream oos) throws IOException { + Parcel p = Parcel.obtain(); // creating empty parcel object + p.writeParcelable(obj, 0); // saving bundle as parcel + byte[] buf = p.marshall(); + oos.writeInt(buf.length); + oos.write(buf); + p.recycle(); + } + + private DataOutputStream getOutputStream() throws IOException { File cacheDir = mContext.getCacheDir(); if (cacheDir == null) { // https://groups.google.com/forum/#!topic/android-developers/-694j87eXVU @@ -65,29 +93,12 @@ public class ParcelableFileCache { } File tempFile = new File(mContext.getCacheDir(), mFilename); - - - DataOutputStream oos = new DataOutputStream(new FileOutputStream(tempFile)); - - try { - oos.writeInt(numEntries); - - while (it.hasNext()) { - Parcel p = Parcel.obtain(); // creating empty parcel object - p.writeParcelable(it.next(), 0); // saving bundle as parcel - byte[] buf = p.marshall(); - oos.writeInt(buf.length); - oos.write(buf); - p.recycle(); - } - } finally { - oos.close(); - } - + return new DataOutputStream(new FileOutputStream(tempFile)); } /** * Reads from cache file and deletes it afterward. Convenience function for readCache(boolean). + * * @return an IteratorWithSize object containing entries read from the cache file * @throws IOException */ @@ -97,10 +108,11 @@ public class ParcelableFileCache { /** * Reads entries from a cache file and returns an IteratorWithSize object containing the entries + * * @param deleteAfterRead if true, the cache file will be deleted after being read * @return an IteratorWithSize object containing entries read from the cache file * @throws IOException if cache directory/parcel import file does not exist, or a read error - * occurs + * occurs */ public IteratorWithSize readCache(final boolean deleteAfterRead) throws IOException { @@ -205,7 +217,6 @@ public class ParcelableFileCache { } public boolean delete() throws IOException { - File cacheDir = mContext.getCacheDir(); if (cacheDir == null) { // https://groups.google.com/forum/#!topic/android-developers/-694j87eXVU @@ -216,12 +227,4 @@ public class ParcelableFileCache { return tempFile.delete(); } - /** As the name implies, this is an extended iterator interface, which - * knows the total number of its entries beforehand. - */ - public static interface IteratorWithSize extends Iterator { - /** Returns the number of total entries in this iterator. */ - int getSize(); - } - } diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_content_paste_white_24dp.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_content_paste_white_24dp.png new file mode 100644 index 000000000..a704a193e Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-hdpi/ic_content_paste_white_24dp.png differ diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_content_paste_white_24dp.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_content_paste_white_24dp.png new file mode 100644 index 000000000..40d05206e Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-mdpi/ic_content_paste_white_24dp.png differ diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_content_paste_white_24dp.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_content_paste_white_24dp.png new file mode 100644 index 000000000..8015d5588 Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-xhdpi/ic_content_paste_white_24dp.png differ diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_content_paste_white_24dp.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_content_paste_white_24dp.png new file mode 100644 index 000000000..3b6283fd2 Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_content_paste_white_24dp.png differ diff --git a/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_content_paste_white_24dp.png b/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_content_paste_white_24dp.png new file mode 100644 index 000000000..42e5cfbc0 Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_content_paste_white_24dp.png differ diff --git a/OpenKeychain/src/main/res/layout/import_keys_activity.xml b/OpenKeychain/src/main/res/layout/import_keys_activity.xml index 6602c4b8f..d3e3ea376 100644 --- a/OpenKeychain/src/main/res/layout/import_keys_activity.xml +++ b/OpenKeychain/src/main/res/layout/import_keys_activity.xml @@ -1,8 +1,7 @@ + android:layout_height="match_parent"> - - - - - - - - + android:layout_height="wrap_content" /> - - - - - - - + diff --git a/OpenKeychain/src/main/res/layout/import_keys_cloud_fragment.xml b/OpenKeychain/src/main/res/layout/import_keys_cloud_fragment.xml deleted file mode 100644 index 1fc8cfdcb..000000000 --- a/OpenKeychain/src/main/res/layout/import_keys_cloud_fragment.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/import_keys_cloud_suggestions_item.xml b/OpenKeychain/src/main/res/layout/import_keys_cloud_suggestions_item.xml new file mode 100644 index 000000000..bd3b9e02a --- /dev/null +++ b/OpenKeychain/src/main/res/layout/import_keys_cloud_suggestions_item.xml @@ -0,0 +1,11 @@ + + \ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/import_keys_file_fragment.xml b/OpenKeychain/src/main/res/layout/import_keys_file_fragment.xml deleted file mode 100644 index c1b9eb276..000000000 --- a/OpenKeychain/src/main/res/layout/import_keys_file_fragment.xml +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/import_keys_list_basic_item.xml b/OpenKeychain/src/main/res/layout/import_keys_list_basic_item.xml new file mode 100644 index 000000000..fc2f3ff26 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/import_keys_list_basic_item.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + +