diff --git a/.gitmodules b/.gitmodules index db9342d20..9d4e0f9dd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,6 +22,3 @@ path = extern/safeslinger-exchange url = https://github.com/open-keychain/exchange-android ignore = dirty -[submodule "extern/snackbar"] - path = extern/snackbar - url = https://github.com/open-keychain/snackbar diff --git a/Graphics/drawables/function.png b/Graphics/drawables/function.png index 9b8983c48..2dd483bef 100644 Binary files a/Graphics/drawables/function.png and b/Graphics/drawables/function.png differ diff --git a/Graphics/drawables/function.svg b/Graphics/drawables/function.svg index 97bc936ba..d5eb4a2fb 100644 --- a/Graphics/drawables/function.svg +++ b/Graphics/drawables/function.svg @@ -16,12 +16,12 @@ inkscape:export-xdpi="90" inkscape:export-ydpi="90" inkscape:output_extension="org.inkscape.output.svg.inkscape" - inkscape:version="0.48.3.1 r9886" + inkscape:version="0.48.5 r10040" sodipodi:version="0.32" width="1024" version="1.1" sodipodi:docname="function.svg" - inkscape:export-filename="/home/schuerm/Projekte/OpenKeychain/Resources/graphics/function.png"> + inkscape:export-filename="/home/schuerm/Projekte/OpenKeychain/Graphics/drawables/function.png"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -3797,205 +3424,6 @@ style="fill:none;stroke:none" id="rect2383" height="193.63591" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="flowPara11203" /> + + + + + + + + + + + + + + + + + + diff --git a/Graphics/screenshots/device-2015-05-06-140816.png b/Graphics/screenshots/device-2015-05-06-140816.png new file mode 100644 index 000000000..705287dd8 Binary files /dev/null and b/Graphics/screenshots/device-2015-05-06-140816.png differ diff --git a/Graphics/screenshots/device-2015-05-06-141500.png b/Graphics/screenshots/device-2015-05-06-141500.png new file mode 100644 index 000000000..930e61c64 Binary files /dev/null and b/Graphics/screenshots/device-2015-05-06-141500.png differ diff --git a/Graphics/screenshots/device-2015-05-06-141508.png b/Graphics/screenshots/device-2015-05-06-141508.png new file mode 100644 index 000000000..e00002fb3 Binary files /dev/null and b/Graphics/screenshots/device-2015-05-06-141508.png differ diff --git a/Graphics/screenshots/device-2015-05-06-141517.png b/Graphics/screenshots/device-2015-05-06-141517.png new file mode 100644 index 000000000..786c6e977 Binary files /dev/null and b/Graphics/screenshots/device-2015-05-06-141517.png differ diff --git a/Graphics/screenshots/device-2015-05-06-141527.png b/Graphics/screenshots/device-2015-05-06-141527.png new file mode 100644 index 000000000..103fc8186 Binary files /dev/null and b/Graphics/screenshots/device-2015-05-06-141527.png differ diff --git a/Graphics/screenshots/device-2015-05-06-141552.png b/Graphics/screenshots/device-2015-05-06-141552.png new file mode 100644 index 000000000..b4bd1ccd0 Binary files /dev/null and b/Graphics/screenshots/device-2015-05-06-141552.png differ diff --git a/Graphics/screenshots/device-2015-05-06-142026.png b/Graphics/screenshots/device-2015-05-06-142026.png new file mode 100644 index 000000000..9b36c9ba4 Binary files /dev/null and b/Graphics/screenshots/device-2015-05-06-142026.png differ diff --git a/OpenKeychain-Test/build.gradle b/OpenKeychain-Test/build.gradle index 1c87fcb4d..2bf35b3d1 100644 --- a/OpenKeychain-Test/build.gradle +++ b/OpenKeychain-Test/build.gradle @@ -17,7 +17,8 @@ dependencies { testCompile 'junit:junit:4.11' testCompile 'com.google.android:android:4.1.1.4' testCompile('com.squareup:fest-android:1.0.8') { exclude module: 'support-v4' } - testCompile ('org.robolectric:robolectric:2.3') { + testCompile 'org.apache.maven:maven-ant-tasks:2.1.3' + testCompile ('org.robolectric:robolectric:2.4') { exclude module: 'classworlds' exclude module: 'maven-artifact' exclude module: 'maven-artifact-manager' diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index 0cc09d663..48fc38ca7 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -6,11 +6,23 @@ dependencies { // NOTE: libraries are pinned to a specific build, see below // from local Android SDK - compile 'com.android.support:support-v4:22.1.0' - compile 'com.android.support:appcompat-v7:22.1.0' + compile 'com.android.support:support-v4:22.1.1' + compile 'com.android.support:appcompat-v7:22.1.1' compile 'com.android.support:recyclerview-v7:22.1.0' compile 'com.android.support:cardview-v7:22.1.0' + // UI testing libs + androidTestCompile 'com.android.support.test:runner:0.2' + androidTestCompile 'com.android.support.test:rules:0.2' + androidTestCompile 'com.android.support.test.espresso:espresso-core:2.1' + androidTestCompile 'com.android.support.test.espresso:espresso-contrib:2.1' + + // Temporary workaround for bug: https://code.google.com/p/android-test-kit/issues/detail?id=136 + // from https://github.com/googlesamples/android-testing/blob/master/build.gradle#L21 + configurations.all { + resolutionStrategy.force 'com.android.support:support-annotations:22.1.1' + } + // JCenter etc. compile 'com.eftimoff:android-patternview:1.0.1@aar' compile 'com.journeyapps:zxing-android-embedded:2.3.0@aar' @@ -23,16 +35,17 @@ dependencies { compile "com.splitwise:tokenautocomplete:1.3.3@aar" compile 'se.emilsjolander:stickylistheaders:2.6.0' compile 'org.sufficientlysecure:html-textview:1.1' - compile 'com.mikepenz.materialdrawer:library:2.7.9@aar' + compile 'com.mikepenz.materialdrawer:library:2.8.2@aar' compile 'com.mikepenz.iconics:library:0.9.1@aar' compile 'com.mikepenz.iconics:octicons-typeface:2.2.0@aar' compile 'com.mikepenz.iconics:meteocons-typeface:1.1.1@aar' compile 'com.mikepenz.iconics:community-material-typeface:1.0.0@aar' compile 'org.thoughtcrime.ssl.pinning:AndroidPinning:1.0.0' + compile 'com.nispok:snackbar:2.10.8' // libs as submodules - compile project(':extern:openpgp-api-lib') - compile project(':extern:openkeychain-api-lib') + compile project(':extern:openpgp-api-lib:openpgp-api') + compile project(':extern:openkeychain-api-lib:openkeychain-intents') compile project(':extern:spongycastle:core') compile project(':extern:spongycastle:pg') compile project(':extern:spongycastle:pkix') @@ -40,15 +53,14 @@ dependencies { compile project(':extern:minidns') compile project(':extern:KeybaseLib:Lib') compile project(':extern:safeslinger-exchange') - compile project(':extern:snackbar:lib') } // Output of ./gradlew -q calculateChecksums // Comment out the libs referenced as git submodules! dependencyVerification { verify = [ - 'com.android.support:support-v4:74cb322740317b11a785eee1a94969426fade946123c4ae3f471276adaaaf54b', - 'com.android.support:appcompat-v7:6cc7fc2df4be0676f78ecfc5d3cda388e59890d11308811944f54efd84b047b7', + 'com.android.support:support-v4:1e2e4d35ac7fd30db5ce3bc177b92e4d5af86acef2ef93e9221599d733346f56', + 'com.android.support:appcompat-v7:9a2355537c2f01cf0b95523605c18606b8d824017e6e94a05c77b0cfc8f21c96', 'com.android.support:recyclerview-v7:522d323079a29bcd76173bd9bc7535223b4af3e5eefef9d9287df1f9e54d0c10', 'com.android.support:cardview-v7:8dc99af71fec000baa4470c3907755264f15f816920861bc015b2babdbb49807', 'com.eftimoff:android-patternview:cec80e7265b8d8278b3c55b5fcdf551e4600ac2c8bf60d8dd76adca538af0b1e', @@ -61,11 +73,12 @@ dependencyVerification { 'com.splitwise:tokenautocomplete:20bee71cc59b3828eb000b684d46ddf738efd56b8fee453a509cd16fda42c8cb', 'se.emilsjolander:stickylistheaders:8c05981ec5725be33f7cee5e68c13f3db49cd5c75f1aaeb04024920b1ef96ad4', 'org.sufficientlysecure:html-textview:ca24b1522be88378634093815ce9ff1b4920c72e7513a045a7846e14069ef988', - 'com.mikepenz.materialdrawer:library:3ef80c6e1ca1b29cfcbb27fa7927c02b2246e068c17fe52283703c4897449923', + 'com.mikepenz.materialdrawer:library:970317ed1a3cb96317f7b8d62ff592b3103eb46dfd68d9b244e7143623dc6d7a', 'com.mikepenz.iconics:library:4698a36ee4c2af765d0a85779c61474d755b90d66a59020105b6760a8a909e9e', 'com.mikepenz.iconics:octicons-typeface:67ed7d456a9ce5f5307b85f955797bfb3dd674e2f6defb31c6b8bbe2ede290be', 'com.mikepenz.iconics:meteocons-typeface:39a8a9e70cd8287cdb119af57a672a41dd09240dba6697f5a0dbda1ccc33298b', 'com.mikepenz.iconics:community-material-typeface:f1c5afee5f0f10d66beb3ed0df977246a02a9c46de4e05d7c0264bcde53b6b7f', + 'com.nispok:snackbar:80bebc8e5d8b3d728cd5f2336e2d0c1cc2a6b7dc4b55d36acd6b75a78265590a', // 'OpenKeychain.extern:openpgp-api-lib:f05a9215cdad3a6597e4c5ece6fcec92b178d218195a3e88d2c0937c48dd9580', // 'OpenKeychain.extern:openkeychain-api-lib:50f6ebb5452d3fdc7be137ccf857a0ff44d55539fcb7b91baef495766ed7f429', // 'com.madgag.spongycastle:core:df8fcc028a95ac5ffab3b78c9163f5cfa672e41cd50128ca55d458b6cfbacf4b', @@ -76,7 +89,7 @@ dependencyVerification { // 'OpenKeychain.extern.KeybaseLib:Lib:c91cda4a75692d8664644cd17d8ac962ce5bc0e266ea26673a639805f1eccbdf', // 'OpenKeychain.extern:safeslinger-exchange:d222721bb35408daaab9f46449364b2657112705ee571d7532f81cbeb9c4a73f', // 'OpenKeychain.extern.snackbar:lib:52357426e5275412e2063bdf6f0e6b957a3ea74da45e0aef35d22d9afc542e23', - 'com.android.support:support-annotations:9c59286413a2bb93e199c73261e58d5af32da7ae0a12cbd075f581a5de1fb446', + 'com.android.support:support-annotations:7bc07519aa613b186001160403bcfd68260fa82c61cc7e83adeedc9b862b94ae', ] } @@ -87,12 +100,21 @@ android { defaultConfig { minSdkVersion 15 targetSdkVersion 22 + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } compileOptions { sourceCompatibility JavaVersion.VERSION_1_7 targetCompatibility JavaVersion.VERSION_1_7 } + + buildTypes { + release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } /* * To sign release build, create file gradle.properties in ~/.gradle/ with this content: @@ -137,6 +159,10 @@ android { dexOptions { preDexLibraries = false } + + packagingOptions { + exclude 'LICENSE.txt' + } } // NOTE: This disables Lint! diff --git a/OpenKeychain/proguard-rules.pro b/OpenKeychain/proguard-rules.pro new file mode 100644 index 000000000..533cae706 --- /dev/null +++ b/OpenKeychain/proguard-rules.pro @@ -0,0 +1,27 @@ +# Add project specific ProGuard rules here. +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + + +# Workaround for Samsung Android 4.2 bug +# https://code.google.com/p/android/issues/detail?id=78377 +# https://code.google.com/p/android/issues/detail?id=78377#c188 +-keepattributes ** +-keep class !android.support.v7.internal.view.menu.**,** {*;} +-dontpreverify +-dontoptimize +-dontshrink +-dontwarn ** +-dontnote ** \ No newline at end of file diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/CreateKeyActivityTest.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/CreateKeyActivityTest.java new file mode 100644 index 000000000..c3741fdef --- /dev/null +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/CreateKeyActivityTest.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2015 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; + +import android.support.test.rule.ActivityTestRule; +import android.support.test.runner.AndroidJUnit4; +import android.text.method.HideReturnsTransformationMethod; +import android.text.method.PasswordTransformationMethod; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.sufficientlysecure.keychain.ui.CreateKeyActivity; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.action.ViewActions.swipeLeft; +import static android.support.test.espresso.action.ViewActions.typeText; +import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.RootMatchers.isDialog; +import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant; +import static android.support.test.espresso.matcher.ViewMatchers.hasSibling; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static org.hamcrest.Matchers.allOf; +import static org.sufficientlysecure.keychain.matcher.EditTextMatchers.withError; +import static org.sufficientlysecure.keychain.matcher.EditTextMatchers.withTransformationMethod; + +@RunWith(AndroidJUnit4.class) +public class CreateKeyActivityTest { + + public static final String SAMPLE_NAME = "Sample Name"; + public static final String SAMPLE_EMAIL = "sample_email@gmail.com"; + public static final String SAMPLE_ADDITIONAL_EMAIL = "sample_additional_email@gmail.com"; + public static final String SAMPLE_PASSWORD = "sample_password"; + + @Rule + public ActivityTestRule mActivityRule = new ActivityTestRule<>(CreateKeyActivity.class); + + @Test + public void testCreateMyKey() { + // Clicks create my key + onView(withId(R.id.create_key_create_key_button)) + .perform(click()); + + // Clicks next with empty name + onView(withId(R.id.create_key_next_button)) + .perform(click()); + onView(withId(R.id.create_key_name)) + .check(matches(withError(R.string.create_key_empty))); + + // Types name and clicks next + onView(withId(R.id.create_key_name)) + .perform(typeText(SAMPLE_NAME)); + onView(withId(R.id.create_key_next_button)) + .perform(click()); + + // Clicks next with empty email + onView(withId(R.id.create_key_next_button)) + .perform(click()); + onView(withId(R.id.create_key_email)) + .check(matches(withError(R.string.create_key_empty))); + + // Types email + onView(withId(R.id.create_key_email)) + .perform(typeText(SAMPLE_EMAIL)); + + // Adds same email as additional email and dismisses the snackbar + onView(withId(R.id.create_key_add_email)) + .perform(click()); + onView(withId(R.id.add_email_address)) + .perform(typeText(SAMPLE_EMAIL)); + onView(withText(android.R.string.ok)) + .inRoot(isDialog()) + .perform(click()); + onView(allOf(withId(R.id.sb__text), withText(R.string.create_key_email_already_exists_text))) + .check(matches(isDisplayed())); + onView(allOf(withId(R.id.sb__text), withText(R.string.create_key_email_already_exists_text))) + .perform(swipeLeft()); + + // Adds additional email + onView(withId(R.id.create_key_add_email)) + .perform(click()); + onView(withId(R.id.add_email_address)) + .perform(typeText(SAMPLE_ADDITIONAL_EMAIL)); + onView(withText(android.R.string.ok)) + .inRoot(isDialog()) + .perform(click()); + onView(withId(R.id.create_key_emails)) + .check(matches(hasDescendant(allOf(withId(R.id.create_key_email_item_email), withText(SAMPLE_ADDITIONAL_EMAIL))))); + + // Removes additional email and clicks next + onView(allOf(withId(R.id.create_key_email_item_delete_button), hasSibling(allOf(withId(R.id.create_key_email_item_email), withText(SAMPLE_ADDITIONAL_EMAIL))))) + .perform(click()) + .check(doesNotExist()); + onView(withId(R.id.create_key_next_button)) + .perform(click(click())); + + // Clicks next with empty password + onView(withId(R.id.create_key_next_button)) + .perform(click()); + onView(withId(R.id.create_key_passphrase)) + .check(matches(withError(R.string.create_key_empty))); + + // Types password + onView(withId(R.id.create_key_passphrase)) + .perform(typeText(SAMPLE_PASSWORD)); + + // Clicks next with empty confirm password + onView(withId(R.id.create_key_next_button)) + .perform(click()); + onView(withId(R.id.create_key_passphrase_again)) + .check(matches(withError(R.string.create_key_passphrases_not_equal))); + + // Types confirm password + onView(withId(R.id.create_key_passphrase_again)) + .perform(typeText(SAMPLE_PASSWORD)); + + // Clicks show password twice and clicks next + onView(withId(R.id.create_key_show_passphrase)) + .perform(click()); + onView(withId(R.id.create_key_passphrase)) + .check(matches(withTransformationMethod(HideReturnsTransformationMethod.class))); + onView(withId(R.id.create_key_passphrase_again)) + .check(matches(withTransformationMethod(HideReturnsTransformationMethod.class))); + onView(withId(R.id.create_key_show_passphrase)) + .perform(click()); + onView(withId(R.id.create_key_passphrase)) + .check(matches(withTransformationMethod(PasswordTransformationMethod.class))); + onView(withId(R.id.create_key_passphrase_again)) + .check(matches(withTransformationMethod(PasswordTransformationMethod.class))); + onView(withId(R.id.create_key_next_button)) + .perform(click()); + + // Verifies name and email + onView(withId(R.id.name)) + .check(matches(withText(SAMPLE_NAME))); + onView(withId(R.id.email)) + .check(matches(withText(SAMPLE_EMAIL))); + + // Verifies backstack + onView(withId(R.id.create_key_back_button)) + .perform(click()); + onView(withId(R.id.create_key_back_button)) + .perform(click()); + onView(withId(R.id.create_key_back_button)) + .perform(click()); + + onView(withId(R.id.create_key_name)) + .check(matches(withText(SAMPLE_NAME))); + onView(withId(R.id.create_key_next_button)) + .perform(click()); + + onView(withId(R.id.create_key_email)) + .check(matches(withText(SAMPLE_EMAIL))); + onView(withId(R.id.create_key_next_button)) + .perform(click()); + + // TODO: Uncomment when fixed in main +// onView(withId(R.id.create_key_passphrase)) +// .check(matches(withText(SAMPLE_PASSWORD))); +// onView(withId(R.id.create_key_passphrase_again)) +// .check(matches(withText(SAMPLE_PASSWORD))); + onView(withId(R.id.create_key_next_button)) + .perform(click()); + + onView(withId(R.id.name)) + .check(matches(withText(SAMPLE_NAME))); + onView(withId(R.id.email)) + .check(matches(withText(SAMPLE_EMAIL))); + + // Clicks create key + onView(withId(R.id.create_key_next_button)) + .perform(click()); + } + +} diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/EditTextMatchers.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/EditTextMatchers.java new file mode 100644 index 000000000..7f2a7953b --- /dev/null +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/EditTextMatchers.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2015 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.matcher; + +import android.content.Context; +import android.text.method.TransformationMethod; +import android.view.View; +import android.widget.EditText; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; + +public class EditTextMatchers { + + public static TypeSafeMatcher withError(final int errorResId) { + return new TypeSafeMatcher() { + + @Override + public boolean matchesSafely(View view) { + Context context = view.getContext(); + + if (view instanceof EditText) { + CharSequence error = ((EditText) view).getError(); + return error != null && error.equals(context.getString(errorResId)); + } + + return false; + } + + @Override + public void describeTo(Description description) { + description.appendText("EditText with error"); + } + + }; + } + + public static TypeSafeMatcher withTransformationMethod(final Class transformationClass) { + return new TypeSafeMatcher() { + + @Override + public boolean matchesSafely(View view) { + if (view instanceof EditText) { + TransformationMethod transformation = ((EditText) view).getTransformationMethod(); + return transformation != null && transformationClass.isInstance(transformation); + } + + return false; + } + + @Override + public void describeTo(Description description) { + description.appendText("EditText with transformation method"); + } + + }; + } + +} diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index 5280b3047..f63cf8823 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -3,8 +3,8 @@ xmlns:tools="http://schemas.android.com/tools" package="org.sufficientlysecure.keychain" android:installLocation="auto" - android:versionCode="31203" - android:versionName="3.2beta3"> + android:versionCode="32100" + android:versionName="3.2.1"> - + @@ -179,14 +178,13 @@ android:name=".ui.EncryptTextActivity" android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:label="@string/title_encrypt_text" - android:windowSoftInputMode="stateHidden" - android:parentActivityName=".ui.MainActivity"> + android:parentActivityName=".ui.MainActivity" + android:windowSoftInputMode="stateHidden"> - - + @@ -206,14 +204,13 @@ android:name=".ui.DecryptTextActivity" android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:label="@string/title_decrypt" - android:windowSoftInputMode="stateHidden" - android:parentActivityName=".ui.MainActivity"> + android:parentActivityName=".ui.MainActivity" + android:windowSoftInputMode="stateHidden"> - - + @@ -233,13 +230,13 @@ android:name=".ui.DecryptFilesActivity" android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:label="@string/title_decrypt" - android:windowSoftInputMode="stateHidden" - android:parentActivityName=".ui.MainActivity"> + android:parentActivityName=".ui.MainActivity" + android:windowSoftInputMode="stateHidden"> - + @@ -259,9 +256,14 @@ + + + - - + @@ -294,7 +296,7 @@ - + @@ -356,7 +358,7 @@ - + @@ -520,7 +522,7 @@ - + @@ -582,7 +584,7 @@ - + @@ -635,17 +637,16 @@ - - + + - + - + - @@ -693,9 +694,9 @@ --> + android:taskAffinity=":Nfc" /> @@ -744,6 +745,11 @@ android:exported="false" android:label="@string/app_name" android:launchMode="singleTop" /> + + android:exported="true" + android:writePermission="org.sufficientlysecure.keychain.WRITE_TEMPORARY_STORAGE" /> diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/TwitterResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/TwitterResource.java index d6b806ee6..73e3d3643 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/TwitterResource.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/TwitterResource.java @@ -32,10 +32,10 @@ import java.util.regex.Pattern; public class TwitterResource extends LinkedTokenResource { - public static final String[] CERT_PINS = new String[] { - // antec Class 3 Secure Server CA - G4 + public static final String[] CERT_PINS = null; /*(new String[] { + // Symantec Class 3 Secure Server CA - G4 "513fb9743870b73440418d30930699ff" - }; + };*/ final String mHandle; final String mTweetId; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java index b48a1da91..86cfc21a3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java @@ -376,6 +376,8 @@ public class ImportExportOperation extends BaseOperation { log.add(LogType.MSG_IMPORT_ERROR, 1); } + ContactSyncAdapterService.requestSync(); + return new ImportKeyResult(resultType, log, newKeys, updatedKeys, badKeys, secret, importedMasterKeyIdsArray); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java index 90ec3053f..bf2349734 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java @@ -1,3 +1,21 @@ +/* + * Copyright (C) 2015 Dominik Schürmann + * Copyright (C) 2015 Vincent Breitmoser + * + * 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.pgp; @@ -32,7 +50,7 @@ public class PgpCertifyOperation { OperationLog log, int indent, CertifyAction action, - Map signedHashes, + Map signedHashes, Date creationTimestamp) { if (!secretKey.isMasterKey()) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java index 8ecb30cdd..9073e81b9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java @@ -178,13 +178,20 @@ public class PgpSignEncryptOperation extends BaseOperation { case PIN: case PATTERN: case PASSPHRASE: { - if (cryptoInput.getPassphrase() == null) { + Passphrase localPassphrase = cryptoInput.getPassphrase(); + if (localPassphrase == null) { + try { + localPassphrase = getCachedPassphrase(signingKeyRing.getMasterKeyId(), signingKey.getKeyId()); + } catch (PassphraseCacheInterface.NoSecretKeyException ignored) { + } + } + if (localPassphrase == null) { log.add(LogType.MSG_PSE_PENDING_PASSPHRASE, indent + 1); return new PgpSignEncryptResult(log, RequiredInputParcel.createRequiredSignPassphrase( signingKeyRing.getMasterKeyId(), signingKey.getKeyId(), cryptoInput.getSignatureTime())); } - if (!signingKey.unlock(cryptoInput.getPassphrase())) { + if (!signingKey.unlock(localPassphrase)) { log.add(LogType.MSG_PSE_ERROR_BAD_PASSPHRASE, indent); return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index deb12a146..11d6728e2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -73,7 +73,7 @@ public class KeychainContract { interface ApiAppsColumns { String PACKAGE_NAME = "package_name"; - String PACKAGE_SIGNATURE = "package_signature"; + String PACKAGE_CERTIFICATE = "package_signature"; } interface ApiAppsAccountsColumns { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index 4a162989f..ff661e494 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -148,7 +148,7 @@ public class KeychainDatabase extends SQLiteOpenHelper { "CREATE TABLE IF NOT EXISTS " + Tables.API_APPS + " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + ApiAppsColumns.PACKAGE_NAME + " TEXT NOT NULL UNIQUE, " - + ApiAppsColumns.PACKAGE_SIGNATURE + " BLOB" + + ApiAppsColumns.PACKAGE_CERTIFICATE + " BLOB" + ")"; private static final String CREATE_API_APPS_ACCOUNTS = 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 655bf19ba..bf56417e9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -50,7 +50,6 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.PgpConstants; -import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.UncachedPublicKey; @@ -1415,7 +1414,7 @@ public class ProviderHelper { private ContentValues contentValueForApiApps(AppSettings appSettings) { ContentValues values = new ContentValues(); values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName()); - values.put(ApiApps.PACKAGE_SIGNATURE, appSettings.getPackageSignature()); + values.put(ApiApps.PACKAGE_CERTIFICATE, appSettings.getPackageSignature()); return values; } @@ -1462,7 +1461,7 @@ public class ProviderHelper { settings.setPackageName(cursor.getString( cursor.getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME))); settings.setPackageSignature(cursor.getBlob( - cursor.getColumnIndex(KeychainContract.ApiApps.PACKAGE_SIGNATURE))); + cursor.getColumnIndex(KeychainContract.ApiApps.PACKAGE_CERTIFICATE))); } } finally { if (cursor != null) { @@ -1554,31 +1553,10 @@ public class ProviderHelper { mContentResolver.insert(uri, values); } - public Set getAllFingerprints(Uri uri) { - Set fingerprints = new HashSet<>(); - String[] projection = new String[]{KeyRings.FINGERPRINT}; - Cursor cursor = mContentResolver.query(uri, projection, null, null, null); - try { - if (cursor != null) { - int fingerprintColumn = cursor.getColumnIndex(KeyRings.FINGERPRINT); - while (cursor.moveToNext()) { - fingerprints.add( - KeyFormattingUtils.convertFingerprintToHex(cursor.getBlob(fingerprintColumn)) - ); - } - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return fingerprints; - } - - public byte[] getApiAppSignature(String packageName) { + public byte[] getApiAppCertificate(String packageName) { Uri queryUri = ApiApps.buildByPackageNameUri(packageName); - String[] projection = new String[]{ApiApps.PACKAGE_SIGNATURE}; + String[] projection = new String[]{ApiApps.PACKAGE_CERTIFICATE}; Cursor cursor = mContentResolver.query(queryUri, projection, null, null, null); try { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java index a65d222da..45f806960 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java @@ -31,10 +31,12 @@ import android.provider.OpenableColumns; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.util.DatabaseUtil; +import org.sufficientlysecure.keychain.util.Log; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.util.UUID; public class TemporaryStorageProvider extends ContentProvider { @@ -44,7 +46,9 @@ public class TemporaryStorageProvider extends ContentProvider { private static final String COLUMN_NAME = "name"; private static final String COLUMN_TIME = "time"; private static final Uri BASE_URI = Uri.parse("content://org.sufficientlysecure.keychain.tempstorage/"); - private static final int DB_VERSION = 1; + private static final int DB_VERSION = 2; + + private static File cacheDir; public static Uri createFile(Context context, String targetName) { ContentValues contentValues = new ContentValues(); @@ -66,7 +70,7 @@ public class TemporaryStorageProvider extends ContentProvider { @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_FILES + " (" + - COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + COLUMN_ID + " TEXT PRIMARY KEY, " + COLUMN_NAME + " TEXT, " + COLUMN_TIME + " INTEGER" + ");"); @@ -74,28 +78,39 @@ public class TemporaryStorageProvider extends ContentProvider { @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + Log.d(Constants.TAG, "Upgrading files db from " + oldVersion + " to " + newVersion); + switch (oldVersion) { + case 1: + db.execSQL("DROP TABLE IF EXISTS files"); + db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_FILES + " (" + + COLUMN_ID + " TEXT PRIMARY KEY, " + + COLUMN_NAME + " TEXT, " + + COLUMN_TIME + " INTEGER" + + ");"); + } } } - private TemporaryStorageDatabase db; + private static TemporaryStorageDatabase db; private File getFile(Uri uri) throws FileNotFoundException { try { - return getFile(Integer.parseInt(uri.getLastPathSegment())); + return getFile(uri.getLastPathSegment()); } catch (NumberFormatException e) { throw new FileNotFoundException(); } } - private File getFile(int id) { - return new File(getContext().getCacheDir(), "temp/" + id); + private File getFile(String id) { + return new File(cacheDir, "temp/" + id); } @Override public boolean onCreate() { db = new TemporaryStorageDatabase(getContext()); - return new File(getContext().getCacheDir(), "temp").mkdirs(); + cacheDir = getContext().getCacheDir(); + return new File(cacheDir, "temp").mkdirs(); } @Override @@ -133,13 +148,15 @@ public class TemporaryStorageProvider extends ContentProvider { if (!values.containsKey(COLUMN_TIME)) { values.put(COLUMN_TIME, System.currentTimeMillis()); } + String uuid = UUID.randomUUID().toString(); + values.put(COLUMN_ID, uuid); int insert = (int) db.getWritableDatabase().insert(TABLE_FILES, null, values); try { - getFile(insert).createNewFile(); + getFile(uuid).createNewFile(); } catch (IOException e) { return null; } - return Uri.withAppendedPath(BASE_URI, Long.toString(insert)); + return Uri.withAppendedPath(BASE_URI, uuid); } @Override @@ -152,7 +169,7 @@ public class TemporaryStorageProvider extends ContentProvider { selectionArgs, null, null, null); if (files != null) { while (files.moveToNext()) { - getFile(files.getInt(0)).delete(); + getFile(files.getString(0)).delete(); } files.close(); return db.getWritableDatabase().delete(TABLE_FILES, selection, selectionArgs); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index c51edf59c..4a8bf9332 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -34,6 +34,7 @@ import org.openintents.openpgp.util.OpenPgpApi; import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel; import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult; import org.sufficientlysecure.keychain.pgp.PgpConstants; @@ -47,6 +48,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity; +import org.sufficientlysecure.keychain.remote.ui.SelectAllowedKeysActivity; import org.sufficientlysecure.keychain.remote.ui.SelectSignKeyIdActivity; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; @@ -205,6 +207,18 @@ public class OpenPgpService extends RemoteService { PendingIntent.FLAG_CANCEL_CURRENT); } + private PendingIntent getSelectAllowedKeysIntent(Intent data) { + // If signature is unknown we return an _additional_ PendingIntent + // to retrieve the missing key + Intent intent = new Intent(getBaseContext(), SelectAllowedKeysActivity.class); + intent.putExtra(SelectAllowedKeysActivity.EXTRA_SERVICE_INTENT, data); + intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(getCurrentCallingPackage())); + + return PendingIntent.getActivity(getBaseContext(), 0, + intent, + PendingIntent.FLAG_CANCEL_CURRENT); + } + private PendingIntent getShowKeyPendingIntent(long masterKeyId) { Intent intent = new Intent(getBaseContext(), ViewKeyActivity.class); intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); @@ -403,6 +417,20 @@ public class OpenPgpService extends RemoteService { .setAdditionalEncryptId(signKeyId); // add sign key for encryption } + // OLD: Even if the message is not signed: Do self-encrypt to account key id + if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) < 7) { + String accName = data.getStringExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME); + // if no account name is given use name "default" + if (TextUtils.isEmpty(accName)) { + accName = "default"; + } + final AccountSettings accSettings = getAccSettings(accName); + if (accSettings == null || (accSettings.getKeyId() == Constants.key.none)) { + return getCreateAccountIntent(data, accName); + } + pseInput.setAdditionalEncryptId(accSettings.getKeyId()); + } + CryptoInputParcel inputParcel = CryptoInputParcelCacheService.getCryptoInputParcel(this, data); if (inputParcel == null) { inputParcel = new CryptoInputParcel(); @@ -476,13 +504,12 @@ public class OpenPgpService extends RemoteService { } String currentPkg = getCurrentCallingPackage(); - Set allowedKeyIds; + Set allowedKeyIds = mProviderHelper.getAllowedKeyIdsForApp( + KeychainContract.ApiAllowedKeys.buildBaseUri(currentPkg)); + if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) < 7) { - allowedKeyIds = mProviderHelper.getAllKeyIdsForApp( - ApiAccounts.buildBaseUri(currentPkg)); - } else { - allowedKeyIds = mProviderHelper.getAllowedKeyIdsForApp( - KeychainContract.ApiAllowedKeys.buildBaseUri(currentPkg)); + allowedKeyIds.addAll(mProviderHelper.getAllKeyIdsForApp( + ApiAccounts.buildBaseUri(currentPkg))); } long inputLength = is.available(); @@ -575,6 +602,15 @@ public class OpenPgpService extends RemoteService { return result; } else { LogEntryParcel errorMsg = pgpResult.getLog().getLast(); + + if (errorMsg.mType == OperationResult.LogType.MSG_DC_ERROR_NO_KEY) { + // allow user to select allowed keys + Intent result = new Intent(); + result.putExtra(OpenPgpApi.RESULT_INTENT, getSelectAllowedKeysIntent(data)); + result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED); + return result; + } + throw new Exception(getString(errorMsg.mType.getMsgId())); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/RemoteService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/RemoteService.java index 59dafb505..e4d4ac49a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/RemoteService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/RemoteService.java @@ -37,6 +37,8 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity; import org.sufficientlysecure.keychain.util.Log; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -45,10 +47,10 @@ import java.util.Arrays; */ public abstract class RemoteService extends Service { - public static class WrongPackageSignatureException extends Exception { + public static class WrongPackageCertificateException extends Exception { private static final long serialVersionUID = -8294642703122196028L; - public WrongPackageSignatureException(String message) { + public WrongPackageCertificateException(String message) { super(message); } } @@ -74,9 +76,9 @@ public abstract class RemoteService extends Service { String packageName = getCurrentCallingPackage(); Log.d(Constants.TAG, "isAllowed packageName: " + packageName); - byte[] packageSignature; + byte[] packageCertificate; try { - packageSignature = getPackageSignature(packageName); + packageCertificate = getPackageCertificate(packageName); } catch (NameNotFoundException e) { Log.e(Constants.TAG, "Should not happen, returning!", e); // return error @@ -91,7 +93,7 @@ public abstract class RemoteService extends Service { Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class); intent.setAction(RemoteServiceActivity.ACTION_REGISTER); intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_NAME, packageName); - intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_SIGNATURE, packageSignature); + intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_SIGNATURE, packageCertificate); intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data); PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0, @@ -105,7 +107,7 @@ public abstract class RemoteService extends Service { return result; } - } catch (WrongPackageSignatureException e) { + } catch (WrongPackageCertificateException e) { Log.e(Constants.TAG, "wrong signature!", e); Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class); @@ -127,14 +129,24 @@ public abstract class RemoteService extends Service { } } - private byte[] getPackageSignature(String packageName) throws NameNotFoundException { + private byte[] getPackageCertificate(String packageName) throws NameNotFoundException { PackageInfo pkgInfo = getPackageManager().getPackageInfo(packageName, PackageManager.GET_SIGNATURES); - Signature[] signatures = pkgInfo.signatures; - // TODO: Only first signature?! - byte[] packageSignature = signatures[0].toByteArray(); + // NOTE: Silly Android API naming: Signatures are actually certificates + Signature[] certificates = pkgInfo.signatures; + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + for (Signature cert : certificates) { + try { + outputStream.write(cert.toByteArray()); + } catch (IOException e) { + throw new RuntimeException("Should not happen! Writing ByteArrayOutputStream to concat certificates failed"); + } + } - return packageSignature; + // Even if an apk has several certificates, these certificates should never change + // Google Play does not allow the introduction of new certificates into an existing apk + // Also see this attack: http://stackoverflow.com/a/10567852 + return outputStream.toByteArray(); } /** @@ -144,9 +156,12 @@ public abstract class RemoteService extends Service { * @return package name */ protected String getCurrentCallingPackage() { - // TODO: - // callingPackages contains more than one entry when sharedUserId has been used... String[] callingPackages = getPackageManager().getPackagesForUid(Binder.getCallingUid()); + + // NOTE: No support for sharedUserIds + // callingPackages contains more than one entry when sharedUserId has been used + // No plans to support sharedUserIds due to many bugs connected to them: + // http://java-hamster.blogspot.de/2010/05/androids-shareduserid.html String currentPkg = callingPackages[0]; Log.d(Constants.TAG, "currentPkg: " + currentPkg); @@ -155,12 +170,12 @@ public abstract class RemoteService extends Service { /** * DEPRECATED API - * + *

* Retrieves AccountSettings from database for the application calling this remote service */ protected AccountSettings getAccSettings(String accountName) { String currentPkg = getCurrentCallingPackage(); - Log.d(Constants.TAG, "getAccSettings accountName: "+ accountName); + Log.d(Constants.TAG, "getAccSettings accountName: " + accountName); Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(currentPkg, accountName); @@ -198,14 +213,14 @@ public abstract class RemoteService extends Service { * * @param allowOnlySelf allow only Keychain app itself * @return true if process is allowed to use this service - * @throws WrongPackageSignatureException + * @throws WrongPackageCertificateException */ - private boolean isCallerAllowed(boolean allowOnlySelf) throws WrongPackageSignatureException { + private boolean isCallerAllowed(boolean allowOnlySelf) throws WrongPackageCertificateException { return isUidAllowed(Binder.getCallingUid(), allowOnlySelf); } private boolean isUidAllowed(int uid, boolean allowOnlySelf) - throws WrongPackageSignatureException { + throws WrongPackageCertificateException { if (android.os.Process.myUid() == uid) { return true; } @@ -229,11 +244,9 @@ public abstract class RemoteService extends Service { /** * Checks if packageName is a registered app for the API. Does not return true for own package! * - * @param packageName - * @return - * @throws WrongPackageSignatureException + * @throws WrongPackageCertificateException */ - private boolean isPackageAllowed(String packageName) throws WrongPackageSignatureException { + private boolean isPackageAllowed(String packageName) throws WrongPackageCertificateException { Log.d(Constants.TAG, "isPackageAllowed packageName: " + packageName); ArrayList allowedPkgs = mProviderHelper.getRegisteredApiApps(); @@ -244,22 +257,22 @@ public abstract class RemoteService extends Service { Log.d(Constants.TAG, "Package is allowed! packageName: " + packageName); // check package signature - byte[] currentSig; + byte[] currentCert; try { - currentSig = getPackageSignature(packageName); + currentCert = getPackageCertificate(packageName); } catch (NameNotFoundException e) { - throw new WrongPackageSignatureException(e.getMessage()); + throw new WrongPackageCertificateException(e.getMessage()); } - byte[] storedSig = mProviderHelper.getApiAppSignature(packageName); - if (Arrays.equals(currentSig, storedSig)) { + byte[] storedCert = mProviderHelper.getApiAppCertificate(packageName); + if (Arrays.equals(currentCert, storedCert)) { Log.d(Constants.TAG, - "Package signature is correct! (equals signature from database)"); + "Package certificate is correct! (equals certificate from database)"); return true; } else { - throw new WrongPackageSignatureException( - "PACKAGE NOT ALLOWED! Signature wrong! (Signature not " + - "equals signature from database)"); + throw new WrongPackageCertificateException( + "PACKAGE NOT ALLOWED! Certificate wrong! (Certificate not " + + "equals certificate from database)"); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java index f312c0d44..5facde64f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java @@ -45,6 +45,7 @@ import org.sufficientlysecure.keychain.util.Log; import java.util.ArrayList; +// TODO: make extensible BaseRemoteServiceActivity and extend these cases from it public class RemoteServiceActivity extends BaseActivity { public static final String ACTION_REGISTER = Constants.INTENT_PREFIX + "API_ACTIVITY_REGISTER"; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectAllowedKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectAllowedKeysActivity.java new file mode 100644 index 000000000..767106ff0 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectAllowedKeysActivity.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2015 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.remote.ui; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.view.View; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; +import org.sufficientlysecure.keychain.util.Log; + +public class SelectAllowedKeysActivity extends BaseActivity { + + public static final String EXTRA_SERVICE_INTENT = "data"; + + private Uri mAppUri; + + private AppSettingsAllowedKeysListFragment mAllowedKeysFragment; + + Intent mServiceData; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Inflate a "Done" custom action bar + setFullScreenDialogDoneClose(R.string.api_settings_save, + new View.OnClickListener() { + @Override + public void onClick(View v) { + save(); + } + }, + new View.OnClickListener() { + @Override + public void onClick(View v) { + cancel(); + } + }); + + Intent intent = getIntent(); + mServiceData = intent.getParcelableExtra(EXTRA_SERVICE_INTENT); + mAppUri = intent.getData(); + if (mAppUri == null) { + Log.e(Constants.TAG, "Intent data missing. Should be Uri of app!"); + finish(); + return; + } else { + Log.d(Constants.TAG, "uri: " + mAppUri); + loadData(savedInstanceState, mAppUri); + } + } + + @Override + protected void initLayout() { + setContentView(R.layout.api_remote_select_allowed_keys); + } + + private void save() { + mAllowedKeysFragment.saveAllowedKeys(); + setResult(Activity.RESULT_OK, mServiceData); + finish(); + } + + private void cancel() { + setResult(Activity.RESULT_CANCELED); + finish(); + } + + private void loadData(Bundle savedInstanceState, Uri appUri) { + Uri allowedKeysUri = appUri.buildUpon().appendPath(KeychainContract.PATH_ALLOWED_KEYS).build(); + Log.d(Constants.TAG, "allowedKeysUri: " + allowedKeysUri); + startListFragments(savedInstanceState, allowedKeysUri); + } + + private void startListFragments(Bundle savedInstanceState, Uri allowedKeysUri) { + // However, if we're being restored from a previous state, + // then we don't need to do anything and should return or else + // we could end up with overlapping fragments. + if (savedInstanceState != null) { + return; + } + + // Create an instance of the fragments + mAllowedKeysFragment = AppSettingsAllowedKeysListFragment.newInstance(allowedKeysUri); + // Add the fragment to the 'fragment_container' FrameLayout + // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! + getSupportFragmentManager().beginTransaction() + .replace(R.id.api_allowed_keys_list_fragment, mAllowedKeysFragment) + .commitAllowingStateLoss(); + // do it immediately! + getSupportFragmentManager().executePendingTransactions(); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CloudImportService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CloudImportService.java index 180109297..249586f6d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CloudImportService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CloudImportService.java @@ -50,18 +50,18 @@ import java.util.concurrent.atomic.AtomicBoolean; */ public class CloudImportService extends Service implements Progressable { - //required as extras from intent + // required as extras from intent public static final String EXTRA_MESSENGER = "messenger"; public static final String EXTRA_DATA = "data"; - //required by data bundle + // required by data bundle public static final String IMPORT_KEY_LIST = "import_key_list"; public static final String IMPORT_KEY_SERVER = "import_key_server"; // indicates a request to cancel the import public static final String ACTION_CANCEL = Constants.INTENT_PREFIX + "CANCEL"; - //tells the spawned threads whether the user has requested a cancel + // tells the spawned threads whether the user has requested a cancel private static AtomicBoolean mActionCancelled = new AtomicBoolean(false); @Override @@ -86,7 +86,7 @@ public class CloudImportService extends Service implements Progressable { public KeyImportAccumulator(int totalKeys) { mTotalKeys = totalKeys; - //ignore updates from ImportExportOperation for now + // ignore updates from ImportExportOperation for now mImportProgressable = new Progressable() { @Override public void setProgress(String message, int current, int total) { @@ -131,20 +131,17 @@ public class CloudImportService extends Service implements Progressable { mSecret += result.mSecret; long[] masterKeyIds = result.getImportedMasterKeyIds(); - for (int i = 0; i < masterKeyIds.length; i++) { - mImportedMasterKeyIds.add(masterKeyIds[i]); + for (long masterKeyId : masterKeyIds) { + mImportedMasterKeyIds.add(masterKeyId); } // 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; - } /** * returns accumulated result of all imports so far - * - * @return */ public ImportKeyResult getConsolidatedImportKeyResult() { @@ -205,7 +202,7 @@ public class CloudImportService extends Service implements Progressable { Bundle data = extras.getBundle(EXTRA_DATA); final String keyServer = data.getString(IMPORT_KEY_SERVER); - //keyList being null (in case key list to be reaad from cache) is checked by importKeys + // keyList being null (in case key list to be reaad from cache) is checked by importKeys final ArrayList keyList = data.getParcelableArrayList(IMPORT_KEY_LIST); // Adding keys to the ThreadPoolExecutor takes time, we don't want to block the main thread @@ -225,7 +222,7 @@ public class CloudImportService extends Service implements Progressable { new ParcelableFileCache<>(this, "key_import.pcl"); int totKeys = 0; Iterator keyListIterator = null; - //either keyList or cache must be null, no guarantees otherwise + // either keyList or cache must be null, no guarantees otherwise if (keyList == null) {//export from cache, copied from ImportExportOperation.importKeyRings try { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java index 0b203614b..e0b728bd4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -20,7 +20,6 @@ package org.sufficientlysecure.keychain.ui; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import org.sufficientlysecure.keychain.R; @@ -84,7 +83,7 @@ public class CreateKeyActivity extends BaseNfcActivity { String nfcUserId = intent.getStringExtra(EXTRA_NFC_USER_ID); byte[] nfcAid = intent.getByteArrayExtra(EXTRA_NFC_AID); - Fragment frag2 = CreateKeyYubiImportFragment.createInstance( + Fragment frag2 = CreateKeyYubiKeyImportFragment.createInstance( nfcFingerprints, nfcAid, nfcUserId); loadFragment(frag2, FragAction.START); @@ -99,7 +98,7 @@ public class CreateKeyActivity extends BaseNfcActivity { if (mFirstTime) { setTitle(R.string.app_name); - setActionBarIcon(R.drawable.ic_launcher); + mToolbar.setNavigationIcon(null); mToolbar.setNavigationOnClickListener(null); } else { setTitle(R.string.title_manage_my_keys); @@ -131,7 +130,7 @@ public class CreateKeyActivity extends BaseNfcActivity { finish(); } catch (PgpKeyNotFoundException e) { - Fragment frag = CreateKeyYubiImportFragment.createInstance( + Fragment frag = CreateKeyYubiKeyImportFragment.createInstance( scannedFingerprints, nfcAid, userId); loadFragment(frag, FragAction.TO_RIGHT); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java index 85e2f8e9d..597f04d6b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java @@ -18,7 +18,6 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; -import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -44,18 +43,17 @@ import org.sufficientlysecure.keychain.ui.widget.EmailEditText; import java.util.ArrayList; import java.util.List; +import java.util.regex.Pattern; public class CreateKeyEmailFragment extends Fragment { + private CreateKeyActivity mCreateKeyActivity; + private EmailEditText mEmailEdit; + private ArrayList mAdditionalEmailModels = new ArrayList<>(); + private EmailAdapter mEmailAdapter; - CreateKeyActivity mCreateKeyActivity; - EmailEditText mEmailEdit; - RecyclerView mEmailsRecyclerView; - View mBackButton; - View mNextButton; - - ArrayList mAdditionalEmailModels; - - EmailAdapter mEmailAdapter; + // NOTE: Do not use more complicated pattern like defined in android.util.Patterns.EMAIL_ADDRESS + // EMAIL_ADDRESS fails for mails with umlauts for example + private static final Pattern EMAIL_PATTERN = Pattern.compile("^[\\S]+@[\\S]+\\.[a-z]+$"); /** * Creates new instance of this fragment @@ -73,14 +71,13 @@ public class CreateKeyEmailFragment extends Fragment { * Checks if text of given EditText is not empty. If it is empty an error is * set and the EditText gets the focus. * - * @param context * @param editText * @return true if EditText is not empty */ - private static boolean isEditTextNotEmpty(Context context, EditText editText) { + private boolean isMainEmailValid(EditText editText) { boolean output = true; - if (editText.getText().length() == 0) { - editText.setError(context.getString(R.string.create_key_empty)); + if (!checkEmail(editText.getText().toString(), false)) { + editText.setError(getString(R.string.create_key_empty)); editText.requestFocus(); output = false; } else { @@ -95,9 +92,9 @@ public class CreateKeyEmailFragment extends Fragment { View view = inflater.inflate(R.layout.create_key_email_fragment, container, false); mEmailEdit = (EmailEditText) view.findViewById(R.id.create_key_email); - mBackButton = view.findViewById(R.id.create_key_back_button); - mNextButton = view.findViewById(R.id.create_key_next_button); - mEmailsRecyclerView = (RecyclerView) view.findViewById(R.id.create_key_emails); + View backButton = view.findViewById(R.id.create_key_back_button); + View nextButton = view.findViewById(R.id.create_key_next_button); + RecyclerView emailsRecyclerView = (RecyclerView) view.findViewById(R.id.create_key_emails); // initial values mEmailEdit.setText(mCreateKeyActivity.mEmail); @@ -106,29 +103,21 @@ public class CreateKeyEmailFragment extends Fragment { if (mCreateKeyActivity.mEmail == null) { mEmailEdit.requestFocus(); } - mBackButton.setOnClickListener(new View.OnClickListener() { + backButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); } }); - mNextButton.setOnClickListener(new View.OnClickListener() { + nextButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { nextClicked(); } }); - mEmailsRecyclerView.setHasFixedSize(true); - mEmailsRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); - mEmailsRecyclerView.setItemAnimator(new DefaultItemAnimator()); - - // initial values - if (mAdditionalEmailModels == null) { - mAdditionalEmailModels = new ArrayList<>(); - if (mCreateKeyActivity.mAdditionalEmails != null) { - mEmailAdapter.addAll(mCreateKeyActivity.mAdditionalEmails); - } - } + emailsRecyclerView.setHasFixedSize(true); + emailsRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); + emailsRecyclerView.setItemAnimator(new DefaultItemAnimator()); if (mEmailAdapter == null) { mEmailAdapter = new EmailAdapter(mAdditionalEmailModels, new View.OnClickListener() { @@ -137,13 +126,77 @@ public class CreateKeyEmailFragment extends Fragment { addEmail(); } }); + + if (mCreateKeyActivity.mAdditionalEmails != null) { + mEmailAdapter.addAll(mCreateKeyActivity.mAdditionalEmails); + } } - mEmailsRecyclerView.setAdapter(mEmailAdapter); + emailsRecyclerView.setAdapter(mEmailAdapter); return view; } + /** + * Checks if a given email is valid + * + * @param email + * @param additionalEmail + * @return + */ + private boolean checkEmail(String email, boolean additionalEmail) { + // check for email format or if the user did any input + if (!isEmailFormatValid(email)) { + Notify.create(getActivity(), + getString(R.string.create_key_email_invalid_email), + Notify.LENGTH_LONG, Notify.Style.ERROR).show(CreateKeyEmailFragment.this); + return false; + } + + // check for duplicated emails + if (!additionalEmail && isEmailDuplicatedInsideAdapter(email) || additionalEmail && + mEmailEdit.getText().length() > 0 && email.equals(mEmailEdit.getText().toString())) { + Notify.create(getActivity(), + getString(R.string.create_key_email_already_exists_text), + Notify.LENGTH_LONG, Notify.Style.ERROR).show(CreateKeyEmailFragment.this); + return false; + } + + return true; + } + + /** + * Checks the email format + * Uses the default Android Email Pattern + * + * @param email + * @return + */ + private boolean isEmailFormatValid(String email) { + // check for email format or if the user did any input + return !(email.length() == 0 || !EMAIL_PATTERN.matcher(email).matches()); + } + + /** + * Checks for duplicated emails inside the additional email adapter. + * + * @param email + * @return + */ + private boolean isEmailDuplicatedInsideAdapter(String email) { + //check for duplicated emails inside the adapter + for (EmailAdapter.ViewModel model : mAdditionalEmailModels) { + if (email.equals(model.email)) { + return true; + } + } + + return false; + } + + /** + * Displays a dialog fragment for the user to input a valid email. + */ private void addEmail() { Handler returnHandler = new Handler() { @Override @@ -153,34 +206,17 @@ public class CreateKeyEmailFragment extends Fragment { String email = data.getString(AddEmailDialogFragment.MESSAGE_DATA_EMAIL); - if (email.length() > 0 && mEmailEdit.getText().length() > 0 && - email.equals(mEmailEdit.getText().toString())) { - Notify.create(getActivity(), - getString(R.string.create_key_email_already_exists_text), - Notify.LENGTH_LONG, Notify.Style.ERROR).show(); - return; + if (checkEmail(email, true)) { + // add new user id + mEmailAdapter.add(email); } - //check for duplicated emails inside the adapter - for (EmailAdapter.ViewModel model : mAdditionalEmailModels) { - if (email.equals(model.email)) { - Notify.create(getActivity(), - getString(R.string.create_key_email_already_exists_text), - Notify.LENGTH_LONG, Notify.Style.ERROR).show(); - return; - } - } - - // add new user id - mEmailAdapter.add(email); } } }; - // Create a new Messenger for the communication back Messenger messenger = new Messenger(returnHandler); AddEmailDialogFragment addEmailDialog = AddEmailDialogFragment.newInstance(messenger); - addEmailDialog.show(getActivity().getSupportFragmentManager(), "addEmailDialog"); } @@ -191,7 +227,7 @@ public class CreateKeyEmailFragment extends Fragment { } private void nextClicked() { - if (isEditTextNotEmpty(getActivity(), mEmailEdit)) { + if (isMainEmailValid(mEmailEdit)) { // save state mCreateKeyActivity.mEmail = mEmailEdit.getText().toString(); mCreateKeyActivity.mAdditionalEmails = getAdditionalEmails(); 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 32173edf7..3379e0a6d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyPassphraseFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyPassphraseFragment.java @@ -21,7 +21,6 @@ import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.support.v4.app.Fragment; -import android.text.Editable; import android.text.method.HideReturnsTransformationMethod; import android.text.method.PasswordTransformationMethod; import android.view.LayoutInflater; @@ -37,9 +36,6 @@ import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; import org.sufficientlysecure.keychain.ui.widget.PassphraseEditText; import org.sufficientlysecure.keychain.util.Passphrase; -import java.util.ArrayList; -import java.util.Arrays; - public class CreateKeyPassphraseFragment extends Fragment { // view @@ -111,8 +107,8 @@ public class CreateKeyPassphraseFragment extends Fragment { // initial values // TODO: using String here is unsafe... if (mCreateKeyActivity.mPassphrase != null) { - mPassphraseEdit.setText(Arrays.toString(mCreateKeyActivity.mPassphrase.getCharArray())); - mPassphraseEditAgain.setText(Arrays.toString(mCreateKeyActivity.mPassphrase.getCharArray())); + mPassphraseEdit.setText(new String(mCreateKeyActivity.mPassphrase.getCharArray())); + mPassphraseEditAgain.setText(new String(mCreateKeyActivity.mPassphrase.getCharArray())); } mPassphraseEdit.requestFocus(); 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 3f56949f5..1a844e6e4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 Dominik Schürmann + * Copyright (C) 2014-2015 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 @@ -18,37 +18,20 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; -import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.Messenger; import android.support.v4.app.Fragment; -import android.support.v7.widget.DefaultItemAnimator; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ImageButton; import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; -import org.sufficientlysecure.keychain.ui.dialog.AddEmailDialogFragment; -import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; -import org.sufficientlysecure.keychain.ui.widget.EmailEditText; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; -import java.util.ArrayList; -import java.util.List; - public class CreateKeyStartFragment extends Fragment { CreateKeyActivity mCreateKeyActivity; @@ -56,8 +39,8 @@ public class CreateKeyStartFragment extends Fragment { View mCreateKey; View mImportKey; View mYubiKey; - TextView mCancel; - public static final int REQUEST_CODE_CREATE_OR_IMPORT_KEY = 0x00007012; + TextView mSkipOrCancel; + public static final int REQUEST_CODE_IMPORT_KEY = 0x00007012; /** * Creates new instance of this fragment @@ -79,12 +62,12 @@ public class CreateKeyStartFragment extends Fragment { mCreateKey = view.findViewById(R.id.create_key_create_key_button); mImportKey = view.findViewById(R.id.create_key_import_button); mYubiKey = view.findViewById(R.id.create_key_yubikey_button); - mCancel = (TextView) view.findViewById(R.id.create_key_cancel); + mSkipOrCancel = (TextView) view.findViewById(R.id.create_key_cancel); if (mCreateKeyActivity.mFirstTime) { - mCancel.setText(R.string.first_time_skip); + mSkipOrCancel.setText(R.string.first_time_skip); } else { - mCancel.setText(R.string.btn_do_not_save); + mSkipOrCancel.setText(R.string.btn_do_not_save); } mCreateKey.setOnClickListener(new View.OnClickListener() { @@ -98,7 +81,7 @@ public class CreateKeyStartFragment extends Fragment { mYubiKey.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - CreateKeyYubiWaitFragment frag = new CreateKeyYubiWaitFragment(); + CreateKeyYubiKeyWaitFragment frag = new CreateKeyYubiKeyWaitFragment(); mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); } }); @@ -108,48 +91,48 @@ public class CreateKeyStartFragment extends Fragment { public void onClick(View v) { Intent intent = new Intent(mCreateKeyActivity, ImportKeysActivity.class); intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN); - startActivityForResult(intent, REQUEST_CODE_CREATE_OR_IMPORT_KEY); + startActivityForResult(intent, REQUEST_CODE_IMPORT_KEY); } }); - mCancel.setOnClickListener(new View.OnClickListener() { + mSkipOrCancel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - finishSetup(null); + if (mCreateKeyActivity.mFirstTime) { + Preferences prefs = Preferences.getPreferences(mCreateKeyActivity); + prefs.setFirstTime(false); + Intent intent = new Intent(mCreateKeyActivity, MainActivity.class); + startActivity(intent); + mCreateKeyActivity.finish(); + } else { + // just finish activity and return data + mCreateKeyActivity.setResult(Activity.RESULT_CANCELED); + mCreateKeyActivity.finish(); + } } }); return view; } - - private void finishSetup(Intent srcData) { - if (mCreateKeyActivity.mFirstTime) { - Preferences prefs = Preferences.getPreferences(mCreateKeyActivity); - prefs.setFirstTime(false); - } - Intent intent = new Intent(mCreateKeyActivity, MainActivity.class); - // give intent through to display notify - if (srcData != null) { - intent.putExtras(srcData); - } - startActivity(intent); - mCreateKeyActivity.finish(); - } - - // workaround for https://code.google.com/p/android/issues/detail?id=61394 -// @Override -// public boolean onKeyDown(int keyCode, KeyEvent event) { -// return keyCode == KeyEvent.KEYCODE_MENU || super.onKeyDown(keyCode, event); -// } - @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - if (requestCode == REQUEST_CODE_CREATE_OR_IMPORT_KEY) { + if (requestCode == REQUEST_CODE_IMPORT_KEY) { if (resultCode == Activity.RESULT_OK) { - finishSetup(data); + if (mCreateKeyActivity.mFirstTime) { + Preferences prefs = Preferences.getPreferences(mCreateKeyActivity); + prefs.setFirstTime(false); + Intent intent = new Intent(mCreateKeyActivity, MainActivity.class); + intent.putExtras(data); + startActivity(intent); + mCreateKeyActivity.finish(); + } else { + // just finish activity and return data + mCreateKeyActivity.setResult(Activity.RESULT_OK, data); + mCreateKeyActivity.finish(); + } } } else { Log.e(Constants.TAG, "No valid request code!"); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiImportFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyImportFragment.java similarity index 98% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiImportFragment.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyImportFragment.java index db62d53c5..4c7d1dfbd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiImportFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyImportFragment.java @@ -48,7 +48,7 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Preferences; -public class CreateKeyYubiImportFragment extends Fragment implements NfcListenerFragment { +public class CreateKeyYubiKeyImportFragment extends Fragment implements NfcListenerFragment { private static final String ARG_FINGERPRINT = "fingerprint"; public static final String ARG_AID = "aid"; @@ -67,7 +67,7 @@ public class CreateKeyYubiImportFragment extends Fragment implements NfcListener public static Fragment createInstance(byte[] scannedFingerprints, byte[] nfcAid, String userId) { - CreateKeyYubiImportFragment frag = new CreateKeyYubiImportFragment(); + CreateKeyYubiKeyImportFragment frag = new CreateKeyYubiKeyImportFragment(); Bundle args = new Bundle(); args.putByteArray(ARG_FINGERPRINT, scannedFingerprints); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiWaitFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyWaitFragment.java similarity index 96% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiWaitFragment.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyWaitFragment.java index 579dddf79..0b8586c0a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiWaitFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyWaitFragment.java @@ -28,7 +28,7 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; -public class CreateKeyYubiWaitFragment extends Fragment { +public class CreateKeyYubiKeyWaitFragment extends Fragment { CreateKeyActivity mCreateKeyActivity; View mBackButton; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java index dce2386b5..c9a590c5b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java @@ -25,7 +25,7 @@ import android.view.View; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.api.OpenKeychainIntents; +import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.util.Log; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java index 6c1902af1..234362edc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java @@ -86,12 +86,12 @@ public class DecryptFilesFragment extends DecryptFragment { */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.decrypt_file_fragment, container, false); + View view = inflater.inflate(R.layout.decrypt_files_fragment, container, false); - mFilename = (TextView) view.findViewById(R.id.decrypt_file_filename); - mDeleteAfter = (CheckBox) view.findViewById(R.id.decrypt_file_delete_after_decryption); - mDecryptButton = view.findViewById(R.id.decrypt_file_action_decrypt); - view.findViewById(R.id.decrypt_file_browse).setOnClickListener(new View.OnClickListener() { + mFilename = (TextView) view.findViewById(R.id.decrypt_files_filename); + mDeleteAfter = (CheckBox) view.findViewById(R.id.decrypt_files_delete_after_decryption); + mDecryptButton = view.findViewById(R.id.decrypt_files_action_decrypt); + view.findViewById(R.id.decrypt_files_browse).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { FileHelper.openDocument(DecryptFilesFragment.this, "*/*", REQUEST_CODE_INPUT); @@ -232,7 +232,6 @@ public class DecryptFilesFragment extends DecryptFragment { returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT); if (pgpResult.success()) { - switch (mCurrentCryptoOperation) { case KeychainIntentService.ACTION_DECRYPT_METADATA: { askForOutputFilename(pgpResult.getDecryptMetadata().getFilename()); @@ -264,9 +263,8 @@ public class DecryptFilesFragment extends DecryptFragment { break; } } - } else { - pgpResult.createNotify(getActivity()).show(); } + pgpResult.createNotify(getActivity()).show(DecryptFilesFragment.this); } } @@ -309,7 +307,7 @@ public class DecryptFilesFragment extends DecryptFragment { } @Override - protected void onVerifyLoaded(boolean verified) { + protected void onVerifyLoaded(boolean hideErrorOverlay) { } } 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 9c51893ce..e9bc42a4d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java @@ -30,6 +30,7 @@ import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.util.Log; import android.view.View; +import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -55,24 +56,24 @@ import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.Preferences; - public abstract class DecryptFragment extends CryptoOperationFragment implements LoaderManager.LoaderCallbacks { public static final int LOADER_ID_UNIFIED = 0; protected LinearLayout mResultLayout; - protected ImageView mEncryptionIcon; protected TextView mEncryptionText; protected ImageView mSignatureIcon; protected TextView mSignatureText; - protected View mSignatureLayout; protected TextView mSignatureName; protected TextView mSignatureEmail; protected TextView mSignatureAction; + private LinearLayout mContentLayout; + private LinearLayout mErrorOverlayLayout; + private OpenPgpSignatureResult mSignatureResult; @Override @@ -82,7 +83,6 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements // NOTE: These views are inside the activity! mResultLayout = (LinearLayout) getActivity().findViewById(R.id.result_main_layout); mResultLayout.setVisibility(View.GONE); - mEncryptionIcon = (ImageView) getActivity().findViewById(R.id.result_encryption_icon); mEncryptionText = (TextView) getActivity().findViewById(R.id.result_encryption_text); mSignatureIcon = (ImageView) getActivity().findViewById(R.id.result_signature_icon); @@ -92,6 +92,17 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements mSignatureEmail = (TextView) getActivity().findViewById(R.id.result_signature_email); mSignatureAction = (TextView) getActivity().findViewById(R.id.result_signature_action); + // Overlay + mContentLayout = (LinearLayout) view.findViewById(R.id.decrypt_content); + mErrorOverlayLayout = (LinearLayout) view.findViewById(R.id.decrypt_error_overlay); + Button vErrorOverlayButton = (Button) view.findViewById(R.id.decrypt_error_overlay_button); + vErrorOverlayButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mErrorOverlayLayout.setVisibility(View.GONE); + mContentLayout.setVisibility(View.VISIBLE); + } + }); } private void lookupUnknownKey(long unknownKeyId) { @@ -113,12 +124,9 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements final ImportKeyResult result = returnData.getParcelable(OperationResult.EXTRA_RESULT); - // if (!result.success()) { - result.createNotify(getActivity()).show(); - // } + result.createNotify(getActivity()).show(); getLoaderManager().restartLoader(LOADER_ID_UNIFIED, null, DecryptFragment.this); - } } }; @@ -153,7 +161,6 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); getActivity().startService(intent); - } private void showKey(long keyId) { @@ -191,6 +198,9 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements getLoaderManager().destroyLoader(LOADER_ID_UNIFIED); + mErrorOverlayLayout.setVisibility(View.GONE); + mContentLayout.setVisibility(View.VISIBLE); + onVerifyLoaded(true); return; @@ -205,7 +215,6 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements } getLoaderManager().restartLoader(LOADER_ID_UNIFIED, null, this); - } private void setSignatureLayoutVisibility(int visibility) { @@ -228,8 +237,6 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements KeychainContract.KeyRings._ID, KeychainContract.KeyRings.MASTER_KEY_ID, KeychainContract.KeyRings.USER_ID, - KeychainContract.KeyRings.IS_REVOKED, - KeychainContract.KeyRings.IS_EXPIRED, KeychainContract.KeyRings.VERIFIED, KeychainContract.KeyRings.HAS_ANY_SECRET, }; @@ -237,10 +244,8 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements @SuppressWarnings("unused") static final int INDEX_MASTER_KEY_ID = 1; static final int INDEX_USER_ID = 2; - static final int INDEX_IS_REVOKED = 3; - static final int INDEX_IS_EXPIRED = 4; - static final int INDEX_VERIFIED = 5; - static final int INDEX_HAS_ANY_SECRET = 6; + static final int INDEX_VERIFIED = 3; + static final int INDEX_HAS_ANY_SECRET = 4; @Override public Loader onCreateLoader(int id, Bundle args) { @@ -282,8 +287,10 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements getActivity(), mSignatureResult.getKeyId())); } - boolean isRevoked = data.getInt(INDEX_IS_REVOKED) != 0; - boolean isExpired = data.getInt(INDEX_IS_EXPIRED) != 0; + // NOTE: Don't use revoked and expired fields from database, they don't show + // revoked/expired subkeys + boolean isRevoked = mSignatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_KEY_REVOKED; + boolean isExpired = mSignatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_KEY_EXPIRED; boolean isVerified = data.getInt(INDEX_VERIFIED) > 0; boolean isYours = data.getInt(INDEX_HAS_ANY_SECRET) != 0; @@ -294,6 +301,9 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements setSignatureLayoutVisibility(View.VISIBLE); setShowAction(signatureKeyId); + mErrorOverlayLayout.setVisibility(View.VISIBLE); + mContentLayout.setVisibility(View.GONE); + onVerifyLoaded(false); } else if (isExpired) { @@ -303,6 +313,22 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements setSignatureLayoutVisibility(View.VISIBLE); setShowAction(signatureKeyId); + mErrorOverlayLayout.setVisibility(View.GONE); + mContentLayout.setVisibility(View.VISIBLE); + + onVerifyLoaded(true); + + } else if (isYours) { + + mSignatureText.setText(R.string.decrypt_result_signature_secret); + KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.VERIFIED); + + setSignatureLayoutVisibility(View.VISIBLE); + setShowAction(signatureKeyId); + + mErrorOverlayLayout.setVisibility(View.GONE); + mContentLayout.setVisibility(View.VISIBLE); + onVerifyLoaded(true); } else if (isYours) { @@ -322,6 +348,9 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements setSignatureLayoutVisibility(View.VISIBLE); setShowAction(signatureKeyId); + mErrorOverlayLayout.setVisibility(View.GONE); + mContentLayout.setVisibility(View.VISIBLE); + onVerifyLoaded(true); } else { @@ -331,6 +360,9 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements setSignatureLayoutVisibility(View.VISIBLE); setShowAction(signatureKeyId); + mErrorOverlayLayout.setVisibility(View.GONE); + mContentLayout.setVisibility(View.VISIBLE); + onVerifyLoaded(true); } @@ -344,7 +376,6 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements } setSignatureLayoutVisibility(View.GONE); - } private void showUnknownKeyStatus() { @@ -388,6 +419,9 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements } }); + mErrorOverlayLayout.setVisibility(View.GONE); + mContentLayout.setVisibility(View.VISIBLE); + onVerifyLoaded(true); break; @@ -399,6 +433,9 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements setSignatureLayoutVisibility(View.GONE); + mErrorOverlayLayout.setVisibility(View.VISIBLE); + mContentLayout.setVisibility(View.GONE); + onVerifyLoaded(false); break; } @@ -407,6 +444,6 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements } - protected abstract void onVerifyLoaded(boolean verified); + protected abstract void onVerifyLoaded(boolean hideErrorOverlay); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java index 728e3ba41..e2eba3947 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2014 Dominik Schürmann + * Copyright (C) 2012-2015 Dominik Schürmann * Copyright (C) 2010-2014 Thialfihar * * This program is free software: you can redistribute it and/or modify @@ -23,16 +23,16 @@ import android.content.Intent; import android.os.Bundle; import android.text.TextUtils; import android.view.View; +import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.api.OpenKeychainIntents; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; +import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.SingletonResult; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.ui.base.BaseActivity; -import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; import java.util.regex.Matcher; @@ -138,8 +138,6 @@ public class DecryptTextActivity extends BaseActivity { /** * Handles all actions with this intent - * - * @param intent */ private void handleActions(Bundle savedInstanceState, Intent intent) { String action = intent.getAction(); @@ -162,10 +160,14 @@ public class DecryptTextActivity extends BaseActivity { if (sharedText != null) { loadFragment(savedInstanceState, sharedText); } else { - Notify.create(this, R.string.error_invalid_data, Notify.Style.ERROR).show(); + Log.e(Constants.TAG, "EXTRA_TEXT does not contain PGP content!"); + Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show(); + finish(); } } else { Log.e(Constants.TAG, "ACTION_SEND received non-plaintext, this should not happen in this activity!"); + Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show(); + finish(); } } else if (ACTION_DECRYPT_TEXT.equals(action)) { Log.d(Constants.TAG, "ACTION_DECRYPT_TEXT"); @@ -176,7 +178,9 @@ public class DecryptTextActivity extends BaseActivity { if (extraText != null) { loadFragment(savedInstanceState, extraText); } else { - Notify.create(this, R.string.error_invalid_data, Notify.Style.ERROR).show(); + Log.e(Constants.TAG, "EXTRA_TEXT does not contain PGP content!"); + Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show(); + finish(); } } else if (ACTION_DECRYPT_FROM_CLIPBOARD.equals(action)) { Log.d(Constants.TAG, "ACTION_DECRYPT_FROM_CLIPBOARD"); @@ -191,6 +195,7 @@ public class DecryptTextActivity extends BaseActivity { } } else if (ACTION_DECRYPT_TEXT.equals(action)) { Log.e(Constants.TAG, "Include the extra 'text' in your Intent!"); + Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show(); finish(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java index 6f576a112..381da6f0d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java @@ -50,8 +50,6 @@ public class DecryptTextFragment extends DecryptFragment { public static final String ARG_CIPHERTEXT = "ciphertext"; // view - private LinearLayout mValidLayout; - private LinearLayout mInvalidLayout; private TextView mText; // model @@ -78,19 +76,8 @@ public class DecryptTextFragment extends DecryptFragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.decrypt_text_fragment, container, false); - mValidLayout = (LinearLayout) view.findViewById(R.id.decrypt_text_valid); - mInvalidLayout = (LinearLayout) view.findViewById(R.id.decrypt_text_invalid); mText = (TextView) view.findViewById(R.id.decrypt_text_plaintext); - Button vInvalidButton = (Button) view.findViewById(R.id.decrypt_text_invalid_button); - vInvalidButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mInvalidLayout.setVisibility(View.GONE); - mValidLayout.setVisibility(View.VISIBLE); - } - }); - return view; } @@ -203,7 +190,6 @@ public class DecryptTextFragment extends DecryptFragment { returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT); if (pgpResult.success()) { - byte[] decryptedMessage = returnData .getByteArray(KeychainIntentService.RESULT_DECRYPTED_BYTES); String displayMessage; @@ -219,15 +205,12 @@ public class DecryptTextFragment extends DecryptFragment { } mText.setText(displayMessage); - pgpResult.createNotify(getActivity()).show(); - // display signature result in activity loadVerifyResult(pgpResult); - } else { - pgpResult.createNotify(getActivity()).show(); // TODO: show also invalid layout with different text? } + pgpResult.createNotify(getActivity()).show(DecryptTextFragment.this); } } }; @@ -244,18 +227,8 @@ public class DecryptTextFragment extends DecryptFragment { } @Override - protected void onVerifyLoaded(boolean verified) { - - mShowMenuOptions = verified; + protected void onVerifyLoaded(boolean hideErrorOverlay) { + mShowMenuOptions = hideErrorOverlay; getActivity().supportInvalidateOptionsMenu(); - - if (verified) { - mInvalidLayout.setVisibility(View.GONE); - mValidLayout.setVisibility(View.VISIBLE); - } else { - mInvalidLayout.setVisibility(View.VISIBLE); - mValidLayout.setVisibility(View.GONE); - } - } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java index 64e908b1a..b3ec60890 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java @@ -28,7 +28,7 @@ import android.view.View; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.api.OpenKeychainIntents; +import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.util.Passphrase; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java index dd6dd6594..52d098adc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java @@ -26,7 +26,7 @@ import android.view.View; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.api.OpenKeychainIntents; +import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; 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 5d9950db6..4cba62d5b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -30,16 +30,16 @@ import android.view.ViewGroup; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.api.OpenKeychainIntents; +import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity; import org.sufficientlysecure.keychain.service.CloudImportService; import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; -import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; @@ -346,60 +346,66 @@ public class ImportKeysActivity extends BaseNfcActivity { mListFragment.loadNew(loaderState); } + private void handleMessage(Message message) { + if (message.arg1 == ServiceProgressHandler.MessageStatus.OKAY.ordinal()) { + // get returned data bundle + Bundle returnData = message.getData(); + if (returnData == null) { + return; + } + final ImportKeyResult result = + returnData.getParcelable(OperationResult.EXTRA_RESULT); + if (result == null) { + Log.e(Constants.TAG, "result == null"); + return; + } + + if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(getIntent().getAction()) + || ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(getIntent().getAction())) { + Intent intent = new Intent(); + intent.putExtra(ImportKeyResult.EXTRA_RESULT, result); + ImportKeysActivity.this.setResult(RESULT_OK, intent); + ImportKeysActivity.this.finish(); + return; + } + if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE.equals(getIntent().getAction())) { + ImportKeysActivity.this.setResult(RESULT_OK, mPendingIntentData); + ImportKeysActivity.this.finish(); + return; + } + + result.createNotify(ImportKeysActivity.this) + .show((ViewGroup) findViewById(R.id.import_snackbar)); + } + } + /** * Import keys with mImportData */ public void importKeys() { - // Message is received after importing is done in CloudImportService - ServiceProgressHandler saveHandler = new ServiceProgressHandler( - this, - getString(R.string.progress_importing), - ProgressDialog.STYLE_HORIZONTAL, - true, - ProgressDialogFragment.ServiceType.CLOUD_IMPORT) { - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first - super.handleMessage(message); - - if (message.arg1 == MessageStatus.OKAY.ordinal()) { - // get returned data bundle - Bundle returnData = message.getData(); - if (returnData == null) { - return; - } - final ImportKeyResult result = - returnData.getParcelable(OperationResult.EXTRA_RESULT); - if (result == null) { - Log.e(Constants.TAG, "result == null"); - return; - } - - if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(getIntent().getAction()) - || ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(getIntent().getAction())) { - Intent intent = new Intent(); - intent.putExtra(ImportKeyResult.EXTRA_RESULT, result); - ImportKeysActivity.this.setResult(RESULT_OK, intent); - ImportKeysActivity.this.finish(); - return; - } - if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE.equals(getIntent().getAction())) { - ImportKeysActivity.this.setResult(RESULT_OK, mPendingIntentData); - ImportKeysActivity.this.finish(); - return; - } - - result.createNotify(ImportKeysActivity.this) - .show((ViewGroup) findViewById(R.id.import_snackbar)); - } - } - }; - ImportKeysListFragment.LoaderState ls = mListFragment.getLoaderState(); if (ls instanceof ImportKeysListFragment.BytesLoaderState) { Log.d(Constants.TAG, "importKeys started"); + ServiceProgressHandler serviceHandler = new ServiceProgressHandler( + this, + getString(R.string.progress_importing), + ProgressDialog.STYLE_HORIZONTAL, + true, + ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) { + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + + ImportKeysActivity.this.handleMessage(message); + } + }; + + // TODO: Currently not using CloudImport here due to https://github.com/open-keychain/open-keychain/issues/1221 // Send all information needed to service to import key in other thread - Intent intent = new Intent(this, CloudImportService.class); + Intent intent = new Intent(this, KeychainIntentService.class); + + intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING); // fill values for this action Bundle data = new Bundle(); @@ -417,14 +423,14 @@ public class ImportKeysActivity extends BaseNfcActivity { new ParcelableFileCache<>(this, "key_import.pcl"); cache.writeCache(selectedEntries); - intent.putExtra(CloudImportService.EXTRA_DATA, data); + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); // Create a new Messenger for the communication back - Messenger messenger = new Messenger(saveHandler); - intent.putExtra(CloudImportService.EXTRA_MESSENGER, messenger); + Messenger messenger = new Messenger(serviceHandler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); // show progress dialog - saveHandler.showProgressDialog(this); + serviceHandler.showProgressDialog(this); // start service with intent startService(intent); @@ -436,6 +442,20 @@ public class ImportKeysActivity extends BaseNfcActivity { } else if (ls instanceof ImportKeysListFragment.CloudLoaderState) { ImportKeysListFragment.CloudLoaderState sls = (ImportKeysListFragment.CloudLoaderState) ls; + ServiceProgressHandler serviceHandler = new ServiceProgressHandler( + this, + getString(R.string.progress_importing), + ProgressDialog.STYLE_HORIZONTAL, + true, + ProgressDialogFragment.ServiceType.CLOUD_IMPORT) { + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + + ImportKeysActivity.this.handleMessage(message); + } + }; + // Send all information needed to service to query keys in other thread Intent intent = new Intent(this, CloudImportService.class); @@ -460,11 +480,11 @@ public class ImportKeysActivity extends BaseNfcActivity { intent.putExtra(CloudImportService.EXTRA_DATA, data); // Create a new Messenger for the communication back - Messenger messenger = new Messenger(saveHandler); + Messenger messenger = new Messenger(serviceHandler); intent.putExtra(CloudImportService.EXTRA_MESSENGER, messenger); // show progress dialog - saveHandler.showProgressDialog(this); + serviceHandler.showProgressDialog(this); // start service with intent startService(intent); 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 29f2511a0..dc8752d1a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java @@ -37,7 +37,7 @@ import com.google.zxing.integration.android.IntentResult; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.api.OpenKeychainIntents; +import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; 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 8c4ea1884..d8c3e0350 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -70,7 +70,6 @@ import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.service.PassphraseCacheService; -import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java index 05cf64092..f571ba1e6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java @@ -64,7 +64,7 @@ public class MainActivity extends AppCompatActivity implements FabContainer { transaction.replace(R.id.main_fragment_container, mainFragment); transaction.commit(); - mToolbar = (Toolbar) findViewById(R.id.activity_main_toolbar); + mToolbar = (Toolbar) findViewById(R.id.toolbar); mToolbar.setTitle(R.string.app_name); setSupportActionBar(mToolbar); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java index 6bd3a9303..fde0f62fd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java @@ -26,6 +26,7 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; +import android.os.ParcelFileDescriptor; import android.support.v4.app.ActivityCompat; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; @@ -42,19 +43,24 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.QrCodeUtils; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.NfcHelper; +import java.io.BufferedWriter; +import java.io.OutputStreamWriter; import java.io.IOException; +import java.io.FileNotFoundException; public class ViewKeyAdvShareFragment extends LoaderFragment implements @@ -175,11 +181,11 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements boolean toClipboard) { try { String content; + byte[] fingerprintData = (byte[]) providerHelper.getGenericData( + KeyRings.buildUnifiedKeyRingUri(dataUri), + Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB); if (fingerprintOnly) { - byte[] data = (byte[]) providerHelper.getGenericData( - KeyRings.buildUnifiedKeyRingUri(dataUri), - Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB); - String fingerprint = KeyFormattingUtils.convertFingerprintToHex(data); + String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintData); if (!toClipboard) { content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint; } else { @@ -213,13 +219,48 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements Intent sendIntent = new Intent(Intent.ACTION_SEND); sendIntent.putExtra(Intent.EXTRA_TEXT, content); sendIntent.setType("text/plain"); + String title; if (fingerprintOnly) { title = getResources().getString(R.string.title_share_fingerprint_with); } else { title = getResources().getString(R.string.title_share_key); } - startActivity(Intent.createChooser(sendIntent, title)); + Intent shareChooser = Intent.createChooser(sendIntent, title); + + // Bluetooth Share will convert text/plain sent via EXTRA_TEXT to HTML + // Add replacement extra to send a text/plain file instead. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + try { + String primaryUserId = UncachedKeyRing.decodeFromData(content.getBytes()). + getPublicKey().getPrimaryUserIdWithFallback(); + + TemporaryStorageProvider shareFileProv = new TemporaryStorageProvider(); + Uri contentUri = TemporaryStorageProvider.createFile(getActivity(), + primaryUserId + Constants.FILE_EXTENSION_ASC); + + BufferedWriter contentWriter = new BufferedWriter(new OutputStreamWriter( + new ParcelFileDescriptor.AutoCloseOutputStream( + shareFileProv.openFile(contentUri, "w")))); + contentWriter.write(content); + contentWriter.close(); + + // create replacement extras inside try{}: + // if file creation fails, just don't add the replacements + Bundle replacements = new Bundle(); + shareChooser.putExtra(Intent.EXTRA_REPLACEMENT_EXTRAS, replacements); + + Bundle bluetoothExtra = new Bundle(sendIntent.getExtras()); + replacements.putBundle("com.android.bluetooth", bluetoothExtra); + + bluetoothExtra.putParcelable(Intent.EXTRA_STREAM, contentUri); + } catch (FileNotFoundException e) { + Log.e(Constants.TAG, "error creating temporary Bluetooth key share file!", e); + Notify.create(getActivity(), R.string.error_bluetooth_file, Notify.Style.ERROR).show(); + } + } + + startActivity(shareChooser); } } catch (PgpGeneralException | IOException e) { Log.e(Constants.TAG, "error processing key!", e); @@ -379,4 +420,4 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements } -} \ No newline at end of file +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java index d9b1fa279..c01a94286 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java @@ -202,6 +202,8 @@ public class ViewKeyFragment extends LoaderFragment implements * In the case of a secret key, "me" (own profile) contact details are loaded. */ private void loadLinkedSystemContact(final long contactId) { + // contact doesn't exist, stop + if(contactId == -1) return; final Context context = mSystemContactName.getContext(); final ContentResolver resolver = context.getContentResolver(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java index f09dc1a4f..3dbae09b6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java @@ -18,15 +18,12 @@ package org.sufficientlysecure.keychain.ui.adapter; -import java.util.Calendar; import java.util.Date; -import java.util.TimeZone; import android.content.Context; import android.database.Cursor; import android.graphics.PorterDuff; import android.support.v4.widget.CursorAdapter; -import android.text.format.DateFormat; import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; @@ -176,9 +173,8 @@ public class KeyAdapter extends CursorAdapter { | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH); - - mCreationDate.setText(context.getString(R.string.label_creation, - dateTime)); + mCreationDate.setText(context.getString(R.string.label_key_created, + dateTime)); mCreationDate.setVisibility(View.VISIBLE); } else { mCreationDate.setVisibility(View.GONE); @@ -281,20 +277,6 @@ public class KeyAdapter extends CursorAdapter { } } - public boolean hasDuplicate() { - return mHasDuplicate; - } - - public String getCreationDate(Context context) { - Calendar creationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - creationCal.setTime(mCreation); - // convert from UTC to time zone of device - creationCal.setTimeZone(TimeZone.getDefault()); - - return context.getString(R.string.label_creation) + ": " - + DateFormat.getDateFormat(context).format(creationCal.getTime()); - } - } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java index 1ccb910d0..6bbf41a88 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java @@ -140,8 +140,7 @@ abstract public class SelectKeyCursorAdapter extends CursorAdapter { | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH); - - h.creation.setText(context.getString(R.string.label_creation, dateTime)); + h.creation.setText(context.getString(R.string.label_key_created, dateTime)); h.creation.setVisibility(View.VISIBLE); } else { h.creation.setVisibility(View.GONE); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEmailDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEmailDialogFragment.java index 5d5ca533e..5b91b9d37 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEmailDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEmailDialogFragment.java @@ -112,9 +112,11 @@ public class AddEmailDialogFragment extends DialogFragment implements OnEditorAc mEmail.post(new Runnable() { @Override public void run() { - InputMethodManager imm = (InputMethodManager) getActivity() - .getSystemService(Context.INPUT_METHOD_SERVICE); - imm.showSoftInput(mEmail, InputMethodManager.SHOW_IMPLICIT); + if(getActivity() != null) { + InputMethodManager imm = (InputMethodManager) getActivity() + .getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(mEmail, InputMethodManager.SHOW_IMPLICIT); + } } }); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java index b58f584c8..af9d175ff 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java @@ -94,12 +94,12 @@ public class ProgressDialogFragment extends DialogFragment { /** Updates progress of dialog */ public void setProgress(String message, int progress, int max) { - if (mIsCancelled) { + ProgressDialog dialog = (ProgressDialog) getDialog(); + + if (mIsCancelled || dialog == null) { return; } - ProgressDialog dialog = (ProgressDialog) getDialog(); - dialog.setMessage(message); dialog.setProgress(progress); dialog.setMax(max); 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 ac7e7a487..3d98034d2 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 @@ -216,7 +216,16 @@ public class KeyFormattingUtils { * @return */ public static String convertFingerprintToHex(byte[] fingerprint) { - return Hex.toHexString(fingerprint, 0, 20).toLowerCase(Locale.ENGLISH); + // NOTE: Even though v3 keys are not imported we need to support both fingerprints for + // display/comparison before import + // Also better cut of unneeded parts, e.g., for fingerprints returned from YubiKeys + if (fingerprint.length < 20) { + // v3 key fingerprint with 128 bit (MD5) + return Hex.toHexString(fingerprint, 0, 16).toLowerCase(Locale.ENGLISH); + } else { + // v4 key fingerprint with 160 bit (SHA1) + return Hex.toHexString(fingerprint, 0, 20).toLowerCase(Locale.ENGLISH); + } } public static long getKeyIdFromFingerprint(byte[] fingerprint) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java index 525bc26ca..63a1aade9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java @@ -136,7 +136,7 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView where += " AND " + KeyRings.USER_ID + " LIKE ?"; return new CursorLoader(getContext(), baseUri, KeyAdapter.PROJECTION, where, - new String[] { "%" + query + "%" }, null); + new String[]{"%" + query + "%"}, null); } mAdapter.setSearchQuery(null); @@ -156,7 +156,7 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView @Override public void showDropDown() { - if (mAdapter.getCursor().isClosed()) { + if (mAdapter == null || mAdapter.getCursor() == null || mAdapter.getCursor().isClosed()) { return; } super.showDropDown(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java index 8cf15a75a..61b7c718b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java @@ -26,7 +26,7 @@ import android.support.v4.app.LoaderManager; import android.support.v4.content.Loader; import android.support.v4.widget.CursorAdapter; import android.support.v7.widget.AppCompatSpinner; -import android.text.format.DateFormat; +import android.text.format.DateUtils; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; @@ -42,10 +42,6 @@ import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.util.Log; -import java.util.Calendar; -import java.util.Date; -import java.util.TimeZone; - /** * Use AppCompatSpinner from AppCompat lib instead of Spinner. Fixes white dropdown icon. * Related: http://stackoverflow.com/a/27713090 @@ -164,14 +160,14 @@ public abstract class KeySpinner extends AppCompatSpinner implements LoaderManag boolean duplicate = cursor.getLong(mIndexDuplicate) > 0; if (duplicate) { - Date creationDate = new Date(cursor.getLong(mIndexCreationDate) * 1000); - Calendar creationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - creationCal.setTime(creationDate); - // convert from UTC to time zone of device - creationCal.setTimeZone(TimeZone.getDefault()); + String dateTime = DateUtils.formatDateTime(context, + cursor.getLong(mIndexCreationDate) * 1000, + DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_SHOW_TIME + | DateUtils.FORMAT_SHOW_YEAR + | DateUtils.FORMAT_ABBREV_MONTH); - vDuplicate.setText(context.getString(R.string.label_creation) + ": " - + DateFormat.getDateFormat(context).format(creationCal.getTime())); + vDuplicate.setText(context.getString(R.string.label_key_created, dateTime)); vDuplicate.setVisibility(View.VISIBLE); } else { vDuplicate.setVisibility(View.GONE); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java index 609288bf1..e1efd5abc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java @@ -446,6 +446,13 @@ public class ContactHelper { writeKeysToMainProfileContact(context, resolver); + writeKeysToNormalContacts(context, resolver); + } + + private static void writeKeysToNormalContacts(Context context, ContentResolver resolver) { + // delete raw contacts flagged for deletion by user so they can be reinserted + deleteFlaggedNormalRawContacts(resolver); + Set deletedKeys = getRawContactMasterKeyIds(resolver); // Load all public Keys from OK @@ -519,6 +526,9 @@ public class ContactHelper { * @param context */ public static void writeKeysToMainProfileContact(Context context, ContentResolver resolver) { + // deletes contacts hidden by the user so they can be reinserted if necessary + deleteFlaggedMainProfileRawContacts(resolver); + Set keysToDelete = getMainProfileMasterKeyIds(resolver); // get all keys which have associated secret keys @@ -585,7 +595,7 @@ public class ContactHelper { * * @param resolver * @param masterKeyId - * @return + * @return number of rows deleted */ private static int deleteMainProfileRawContactByMasterKeyId(ContentResolver resolver, long masterKeyId) { @@ -602,6 +612,28 @@ public class ContactHelper { }); } + /** + * deletes all raw contact entries in the "me" contact flagged for deletion ('hidden'), + * presumably by the user + * + * @param resolver + * @return number of raw contacts deleted + */ + private static int deleteFlaggedMainProfileRawContacts(ContentResolver resolver) { + // CALLER_IS_SYNCADAPTER allows us to actually wipe the RawContact from the device, otherwise + // would be just flagged for deletion + Uri deleteUri = ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI.buildUpon(). + appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build(); + + return resolver.delete(deleteUri, + ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " + + ContactsContract.RawContacts.DELETED + "=?", + new String[]{ + Constants.ACCOUNT_TYPE, + "1" + }); + } + /** * Delete all raw contacts associated to OpenKeychain, including those from "me" contact * defined by ContactsContract.Profile @@ -677,6 +709,21 @@ public class ContactHelper { }); } + private static int deleteFlaggedNormalRawContacts(ContentResolver resolver) { + // CALLER_IS_SYNCADAPTER allows us to actually wipe the RawContact from the device, otherwise + // would be just flagged for deletion + Uri deleteUri = ContactsContract.RawContacts.CONTENT_URI.buildUpon(). + appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build(); + + return resolver.delete(deleteUri, + ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " + + ContactsContract.RawContacts.DELETED + "=?", + new String[]{ + Constants.ACCOUNT_TYPE, + "1" + }); + } + /** * @return a set of all key master key ids currently present in the contact db */ diff --git a/OpenKeychain/src/main/res/layout/api_remote_select_allowed_keys.xml b/OpenKeychain/src/main/res/layout/api_remote_select_allowed_keys.xml new file mode 100644 index 000000000..e052ff333 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/api_remote_select_allowed_keys.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/create_yubikey_wait_fragment.xml b/OpenKeychain/src/main/res/layout/create_yubikey_wait_fragment.xml index c7f9821eb..4e4b53118 100644 --- a/OpenKeychain/src/main/res/layout/create_yubikey_wait_fragment.xml +++ b/OpenKeychain/src/main/res/layout/create_yubikey_wait_fragment.xml @@ -22,8 +22,7 @@ android:layout_marginTop="16dp" android:layout_marginLeft="8dp" android:textAppearance="?android:attr/textAppearanceMedium" - android:text="Hold Yubikey against device dawg" - /> + android:text="@string/yubikey_create" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/decrypt_files_activity.xml b/OpenKeychain/src/main/res/layout/decrypt_files_activity.xml index 3d214dbf6..6c8a2e859 100644 --- a/OpenKeychain/src/main/res/layout/decrypt_files_activity.xml +++ b/OpenKeychain/src/main/res/layout/decrypt_files_activity.xml @@ -5,7 +5,7 @@ + layout="@layout/toolbar_result_decrypt" /> + + + + + + +