Merge tag 'v3.2.1' into linked-identities

Version 3.2.1

Conflicts:
	OpenKeychain/build.gradle
	OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java
	OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java
	OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java
	OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java
	OpenKeychain/src/main/res/layout/decrypt_text_fragment.xml
	OpenKeychain/src/main/res/values/strings.xml
This commit is contained in:
Vincent Breitmoser
2015-05-11 17:28:34 +02:00
104 changed files with 1597 additions and 1339 deletions

3
.gitmodules vendored
View File

@@ -22,6 +22,3 @@
path = extern/safeslinger-exchange path = extern/safeslinger-exchange
url = https://github.com/open-keychain/exchange-android url = https://github.com/open-keychain/exchange-android
ignore = dirty ignore = dirty
[submodule "extern/snackbar"]
path = extern/snackbar
url = https://github.com/open-keychain/snackbar

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 63 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 197 KiB

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

View File

@@ -17,7 +17,8 @@ dependencies {
testCompile 'junit:junit:4.11' testCompile 'junit:junit:4.11'
testCompile 'com.google.android:android:4.1.1.4' testCompile 'com.google.android:android:4.1.1.4'
testCompile('com.squareup:fest-android:1.0.8') { exclude module: 'support-v4' } 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: 'classworlds'
exclude module: 'maven-artifact' exclude module: 'maven-artifact'
exclude module: 'maven-artifact-manager' exclude module: 'maven-artifact-manager'

View File

@@ -6,11 +6,23 @@ dependencies {
// NOTE: libraries are pinned to a specific build, see below // NOTE: libraries are pinned to a specific build, see below
// from local Android SDK // from local Android SDK
compile 'com.android.support:support-v4:22.1.0' compile 'com.android.support:support-v4:22.1.1'
compile 'com.android.support:appcompat-v7:22.1.0' compile 'com.android.support:appcompat-v7:22.1.1'
compile 'com.android.support:recyclerview-v7:22.1.0' compile 'com.android.support:recyclerview-v7:22.1.0'
compile 'com.android.support:cardview-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. // JCenter etc.
compile 'com.eftimoff:android-patternview:1.0.1@aar' compile 'com.eftimoff:android-patternview:1.0.1@aar'
compile 'com.journeyapps:zxing-android-embedded:2.3.0@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 "com.splitwise:tokenautocomplete:1.3.3@aar"
compile 'se.emilsjolander:stickylistheaders:2.6.0' compile 'se.emilsjolander:stickylistheaders:2.6.0'
compile 'org.sufficientlysecure:html-textview:1.1' 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:library:0.9.1@aar'
compile 'com.mikepenz.iconics:octicons-typeface:2.2.0@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:meteocons-typeface:1.1.1@aar'
compile 'com.mikepenz.iconics:community-material-typeface:1.0.0@aar' compile 'com.mikepenz.iconics:community-material-typeface:1.0.0@aar'
compile 'org.thoughtcrime.ssl.pinning:AndroidPinning:1.0.0' compile 'org.thoughtcrime.ssl.pinning:AndroidPinning:1.0.0'
compile 'com.nispok:snackbar:2.10.8'
// libs as submodules // libs as submodules
compile project(':extern:openpgp-api-lib') compile project(':extern:openpgp-api-lib:openpgp-api')
compile project(':extern:openkeychain-api-lib') compile project(':extern:openkeychain-api-lib:openkeychain-intents')
compile project(':extern:spongycastle:core') compile project(':extern:spongycastle:core')
compile project(':extern:spongycastle:pg') compile project(':extern:spongycastle:pg')
compile project(':extern:spongycastle:pkix') compile project(':extern:spongycastle:pkix')
@@ -40,15 +53,14 @@ dependencies {
compile project(':extern:minidns') compile project(':extern:minidns')
compile project(':extern:KeybaseLib:Lib') compile project(':extern:KeybaseLib:Lib')
compile project(':extern:safeslinger-exchange') compile project(':extern:safeslinger-exchange')
compile project(':extern:snackbar:lib')
} }
// Output of ./gradlew -q calculateChecksums // Output of ./gradlew -q calculateChecksums
// Comment out the libs referenced as git submodules! // Comment out the libs referenced as git submodules!
dependencyVerification { dependencyVerification {
verify = [ verify = [
'com.android.support:support-v4:74cb322740317b11a785eee1a94969426fade946123c4ae3f471276adaaaf54b', 'com.android.support:support-v4:1e2e4d35ac7fd30db5ce3bc177b92e4d5af86acef2ef93e9221599d733346f56',
'com.android.support:appcompat-v7:6cc7fc2df4be0676f78ecfc5d3cda388e59890d11308811944f54efd84b047b7', 'com.android.support:appcompat-v7:9a2355537c2f01cf0b95523605c18606b8d824017e6e94a05c77b0cfc8f21c96',
'com.android.support:recyclerview-v7:522d323079a29bcd76173bd9bc7535223b4af3e5eefef9d9287df1f9e54d0c10', 'com.android.support:recyclerview-v7:522d323079a29bcd76173bd9bc7535223b4af3e5eefef9d9287df1f9e54d0c10',
'com.android.support:cardview-v7:8dc99af71fec000baa4470c3907755264f15f816920861bc015b2babdbb49807', 'com.android.support:cardview-v7:8dc99af71fec000baa4470c3907755264f15f816920861bc015b2babdbb49807',
'com.eftimoff:android-patternview:cec80e7265b8d8278b3c55b5fcdf551e4600ac2c8bf60d8dd76adca538af0b1e', 'com.eftimoff:android-patternview:cec80e7265b8d8278b3c55b5fcdf551e4600ac2c8bf60d8dd76adca538af0b1e',
@@ -61,11 +73,12 @@ dependencyVerification {
'com.splitwise:tokenautocomplete:20bee71cc59b3828eb000b684d46ddf738efd56b8fee453a509cd16fda42c8cb', 'com.splitwise:tokenautocomplete:20bee71cc59b3828eb000b684d46ddf738efd56b8fee453a509cd16fda42c8cb',
'se.emilsjolander:stickylistheaders:8c05981ec5725be33f7cee5e68c13f3db49cd5c75f1aaeb04024920b1ef96ad4', 'se.emilsjolander:stickylistheaders:8c05981ec5725be33f7cee5e68c13f3db49cd5c75f1aaeb04024920b1ef96ad4',
'org.sufficientlysecure:html-textview:ca24b1522be88378634093815ce9ff1b4920c72e7513a045a7846e14069ef988', 'org.sufficientlysecure:html-textview:ca24b1522be88378634093815ce9ff1b4920c72e7513a045a7846e14069ef988',
'com.mikepenz.materialdrawer:library:3ef80c6e1ca1b29cfcbb27fa7927c02b2246e068c17fe52283703c4897449923', 'com.mikepenz.materialdrawer:library:970317ed1a3cb96317f7b8d62ff592b3103eb46dfd68d9b244e7143623dc6d7a',
'com.mikepenz.iconics:library:4698a36ee4c2af765d0a85779c61474d755b90d66a59020105b6760a8a909e9e', 'com.mikepenz.iconics:library:4698a36ee4c2af765d0a85779c61474d755b90d66a59020105b6760a8a909e9e',
'com.mikepenz.iconics:octicons-typeface:67ed7d456a9ce5f5307b85f955797bfb3dd674e2f6defb31c6b8bbe2ede290be', 'com.mikepenz.iconics:octicons-typeface:67ed7d456a9ce5f5307b85f955797bfb3dd674e2f6defb31c6b8bbe2ede290be',
'com.mikepenz.iconics:meteocons-typeface:39a8a9e70cd8287cdb119af57a672a41dd09240dba6697f5a0dbda1ccc33298b', 'com.mikepenz.iconics:meteocons-typeface:39a8a9e70cd8287cdb119af57a672a41dd09240dba6697f5a0dbda1ccc33298b',
'com.mikepenz.iconics:community-material-typeface:f1c5afee5f0f10d66beb3ed0df977246a02a9c46de4e05d7c0264bcde53b6b7f', 'com.mikepenz.iconics:community-material-typeface:f1c5afee5f0f10d66beb3ed0df977246a02a9c46de4e05d7c0264bcde53b6b7f',
'com.nispok:snackbar:80bebc8e5d8b3d728cd5f2336e2d0c1cc2a6b7dc4b55d36acd6b75a78265590a',
// 'OpenKeychain.extern:openpgp-api-lib:f05a9215cdad3a6597e4c5ece6fcec92b178d218195a3e88d2c0937c48dd9580', // 'OpenKeychain.extern:openpgp-api-lib:f05a9215cdad3a6597e4c5ece6fcec92b178d218195a3e88d2c0937c48dd9580',
// 'OpenKeychain.extern:openkeychain-api-lib:50f6ebb5452d3fdc7be137ccf857a0ff44d55539fcb7b91baef495766ed7f429', // 'OpenKeychain.extern:openkeychain-api-lib:50f6ebb5452d3fdc7be137ccf857a0ff44d55539fcb7b91baef495766ed7f429',
// 'com.madgag.spongycastle:core:df8fcc028a95ac5ffab3b78c9163f5cfa672e41cd50128ca55d458b6cfbacf4b', // 'com.madgag.spongycastle:core:df8fcc028a95ac5ffab3b78c9163f5cfa672e41cd50128ca55d458b6cfbacf4b',
@@ -76,7 +89,7 @@ dependencyVerification {
// 'OpenKeychain.extern.KeybaseLib:Lib:c91cda4a75692d8664644cd17d8ac962ce5bc0e266ea26673a639805f1eccbdf', // 'OpenKeychain.extern.KeybaseLib:Lib:c91cda4a75692d8664644cd17d8ac962ce5bc0e266ea26673a639805f1eccbdf',
// 'OpenKeychain.extern:safeslinger-exchange:d222721bb35408daaab9f46449364b2657112705ee571d7532f81cbeb9c4a73f', // 'OpenKeychain.extern:safeslinger-exchange:d222721bb35408daaab9f46449364b2657112705ee571d7532f81cbeb9c4a73f',
// 'OpenKeychain.extern.snackbar:lib:52357426e5275412e2063bdf6f0e6b957a3ea74da45e0aef35d22d9afc542e23', // 'OpenKeychain.extern.snackbar:lib:52357426e5275412e2063bdf6f0e6b957a3ea74da45e0aef35d22d9afc542e23',
'com.android.support:support-annotations:9c59286413a2bb93e199c73261e58d5af32da7ae0a12cbd075f581a5de1fb446', 'com.android.support:support-annotations:7bc07519aa613b186001160403bcfd68260fa82c61cc7e83adeedc9b862b94ae',
] ]
} }
@@ -87,12 +100,21 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 15 minSdkVersion 15
targetSdkVersion 22 targetSdkVersion 22
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7 sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility 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: * To sign release build, create file gradle.properties in ~/.gradle/ with this content:
@@ -137,6 +159,10 @@ android {
dexOptions { dexOptions {
preDexLibraries = false preDexLibraries = false
} }
packagingOptions {
exclude 'LICENSE.txt'
}
} }
// NOTE: This disables Lint! // NOTE: This disables Lint!

27
OpenKeychain/proguard-rules.pro vendored Normal file
View File

@@ -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 **

View File

@@ -0,0 +1,194 @@
/*
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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<CreateKeyActivity> 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());
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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<View> withError(final int errorResId) {
return new TypeSafeMatcher<View>() {
@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<View> withTransformationMethod(final Class<? extends TransformationMethod> transformationClass) {
return new TypeSafeMatcher<View>() {
@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");
}
};
}
}

View File

@@ -3,8 +3,8 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="org.sufficientlysecure.keychain" package="org.sufficientlysecure.keychain"
android:installLocation="auto" android:installLocation="auto"
android:versionCode="31203" android:versionCode="32100"
android:versionName="3.2beta3"> android:versionName="3.2.1">
<!-- <!--
General remarks General remarks
@@ -15,7 +15,7 @@
Association of file types to Keychain Association of file types to Keychain
===================================== =====================================
General remarks about file ending conventions: General remarks about file ending conventions:
- *.gpg for binary files - *.gpg,*.pgp for binary files
- *.asc for ascii armored files The actual content can be anything. - *.asc for ascii armored files The actual content can be anything.
The file ending only shows if it is binary or ascii encoded. The file ending only shows if it is binary or ascii encoded.
@@ -73,8 +73,8 @@
android:allowBackup="false" android:allowBackup="false"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:icon="@drawable/ic_launcher" android:icon="@drawable/ic_launcher"
android:theme="@style/KeychainTheme" android:label="@string/app_name"
android:label="@string/app_name"> android:theme="@style/KeychainTheme">
<activity <activity
android:name=".ui.MainActivity" android:name=".ui.MainActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
@@ -88,11 +88,11 @@
</activity> </activity>
<activity <activity
android:name=".ui.CreateKeyActivity" android:name=".ui.CreateKeyActivity"
android:windowSoftInputMode="adjustResize" android:allowTaskReparenting="true"
android:label="@string/title_manage_my_keys" android:label="@string/title_manage_my_keys"
android:launchMode="singleTop" android:launchMode="singleTop"
android:allowTaskReparenting="true" android:parentActivityName=".ui.MainActivity"
android:parentActivityName=".ui.MainActivity"> android:windowSoftInputMode="adjustResize">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value=".ui.MainActivity" /> android:value=".ui.MainActivity" />
@@ -136,8 +136,8 @@
android:name=".ui.SafeSlingerActivity" android:name=".ui.SafeSlingerActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_exchange_keys" android:label="@string/title_exchange_keys"
android:windowSoftInputMode="stateHidden" android:parentActivityName=".ui.MainActivity"
android:parentActivityName=".ui.MainActivity"> android:windowSoftInputMode="stateHidden">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value=".ui.MainActivity" /> android:value=".ui.MainActivity" />
@@ -146,14 +146,13 @@
android:name=".ui.EncryptFilesActivity" android:name=".ui.EncryptFilesActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_encrypt_files" android:label="@string/title_encrypt_files"
android:windowSoftInputMode="stateHidden" android:parentActivityName=".ui.MainActivity"
android:parentActivityName=".ui.MainActivity"> android:windowSoftInputMode="stateHidden">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value=".ui.MainActivity" /> android:value=".ui.MainActivity" />
<!-- Keychain's own Actions --> <!-- ENCRYPT_DATA with data Uri -->
<!-- ENCRYPT with data Uri -->
<intent-filter> <intent-filter>
<action android:name="org.sufficientlysecure.keychain.action.ENCRYPT_DATA" /> <action android:name="org.sufficientlysecure.keychain.action.ENCRYPT_DATA" />
@@ -179,14 +178,13 @@
android:name=".ui.EncryptTextActivity" android:name=".ui.EncryptTextActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_encrypt_text" android:label="@string/title_encrypt_text"
android:windowSoftInputMode="stateHidden" android:parentActivityName=".ui.MainActivity"
android:parentActivityName=".ui.MainActivity"> android:windowSoftInputMode="stateHidden">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value=".ui.MainActivity" /> android:value=".ui.MainActivity" />
<!-- Keychain's own Actions --> <!-- ENCRYPT_TEXT with text as extra -->
<!-- ENCRYPT with text as extra -->
<intent-filter> <intent-filter>
<action android:name="org.sufficientlysecure.keychain.action.ENCRYPT_TEXT" /> <action android:name="org.sufficientlysecure.keychain.action.ENCRYPT_TEXT" />
@@ -206,14 +204,13 @@
android:name=".ui.DecryptTextActivity" android:name=".ui.DecryptTextActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_decrypt" android:label="@string/title_decrypt"
android:windowSoftInputMode="stateHidden" android:parentActivityName=".ui.MainActivity"
android:parentActivityName=".ui.MainActivity"> android:windowSoftInputMode="stateHidden">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value=".ui.MainActivity" /> android:value=".ui.MainActivity" />
<!-- Keychain's own Actions --> <!-- DECRYPT_TEXT with text as extra -->
<!-- DECRYPT with text as extra -->
<intent-filter> <intent-filter>
<action android:name="org.sufficientlysecure.keychain.action.DECRYPT_TEXT" /> <action android:name="org.sufficientlysecure.keychain.action.DECRYPT_TEXT" />
@@ -233,13 +230,13 @@
android:name=".ui.DecryptFilesActivity" android:name=".ui.DecryptFilesActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_decrypt" android:label="@string/title_decrypt"
android:windowSoftInputMode="stateHidden" android:parentActivityName=".ui.MainActivity"
android:parentActivityName=".ui.MainActivity"> android:windowSoftInputMode="stateHidden">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value=".ui.MainActivity" /> android:value=".ui.MainActivity" />
<!-- VIEW with mimeType application/octet-stream, application/pgp and text/pgp --> <!-- VIEW with mimeTypes -->
<intent-filter android:label="@string/intent_send_decrypt"> <intent-filter android:label="@string/intent_send_decrypt">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@@ -259,9 +256,14 @@
<!-- non-standard MIME types found in the wild --> <!-- non-standard MIME types found in the wild -->
<data android:mimeType="application/pgp" /> <data android:mimeType="application/pgp" />
<data android:mimeType="text/pgp" /> <data android:mimeType="text/pgp" />
<!--
This links to attached asc files in AOSP mail. It is deactivated because of
https://github.com/open-keychain/open-keychain/issues/290
-->
<!--<data android:mimeType="text/plain" />-->
</intent-filter> </intent-filter>
<!-- Keychain's own Actions --> <!-- DECRYPT_DATA with data Uri -->
<!-- DECRYPT with data Uri -->
<intent-filter> <intent-filter>
<action android:name="org.sufficientlysecure.keychain.action.DECRYPT_DATA" /> <action android:name="org.sufficientlysecure.keychain.action.DECRYPT_DATA" />
@@ -294,7 +296,7 @@
<data android:scheme="file" /> <data android:scheme="file" />
<data android:scheme="content" /> <data android:scheme="content" />
<!-- GnuPG ASCII data, mostly keys, but sometimes signatures and encrypted data --> <!-- ASCII data, mostly keys, but sometimes signatures and encrypted data -->
<data android:pathPattern=".*\\.asc" /> <data android:pathPattern=".*\\.asc" />
<data android:pathPattern=".*\\..*\\.asc" /> <data android:pathPattern=".*\\..*\\.asc" />
<data android:pathPattern=".*\\..*\\..*\\.asc" /> <data android:pathPattern=".*\\..*\\..*\\.asc" />
@@ -356,7 +358,7 @@
<data android:mimeType="*/*" /> <data android:mimeType="*/*" />
<!-- GnuPG ASCII data, mostly keys, but sometimes signatures and encrypted data --> <!-- ASCII data, mostly keys, but sometimes signatures and encrypted data -->
<data android:pathPattern=".*\\.asc" /> <data android:pathPattern=".*\\.asc" />
<data android:pathPattern=".*\\..*\\.asc" /> <data android:pathPattern=".*\\..*\\.asc" />
<data android:pathPattern=".*\\..*\\..*\\.asc" /> <data android:pathPattern=".*\\..*\\..*\\.asc" />
@@ -520,7 +522,7 @@
<data android:scheme="file" /> <data android:scheme="file" />
<data android:scheme="content" /> <data android:scheme="content" />
<!-- GnuPG ASCII data, mostly keys, but sometimes signatures and encrypted data --> <!-- ASCII data, mostly keys, but sometimes signatures and encrypted data -->
<data android:pathPattern=".*\\.asc" /> <data android:pathPattern=".*\\.asc" />
<data android:pathPattern=".*\\..*\\.asc" /> <data android:pathPattern=".*\\..*\\.asc" />
<data android:pathPattern=".*\\..*\\..*\\.asc" /> <data android:pathPattern=".*\\..*\\..*\\.asc" />
@@ -582,7 +584,7 @@
<data android:mimeType="*/*" /> <data android:mimeType="*/*" />
<!-- GnuPG ASCII data, mostly keys, but sometimes signatures and encrypted data --> <!-- ASCII data, mostly keys, but sometimes signatures and encrypted data -->
<data android:pathPattern=".*\\.asc" /> <data android:pathPattern=".*\\.asc" />
<data android:pathPattern=".*\\..*\\.asc" /> <data android:pathPattern=".*\\..*\\.asc" />
<data android:pathPattern=".*\\..*\\..*\\.asc" /> <data android:pathPattern=".*\\..*\\..*\\.asc" />
@@ -635,17 +637,16 @@
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:scheme="https"/> <data android:scheme="https" />
<data android:scheme="http"/> <data android:scheme="http" />
<!-- if we don't specify a host, pathPattern will be ignored--> <!-- if we don't specify a host, pathPattern will be ignored-->
<data android:host="*"/> <data android:host="*" />
<!-- convention for keyserver paths specified by internet draft <!-- convention for keyserver paths specified by internet draft
draft-shaw-openpgp-hkp-00.txt draft-shaw-openpgp-hkp-00.txt
(http://tools.ietf.org/html/draft-shaw-openpgp-hkp-00#section-3) --> (http://tools.ietf.org/html/draft-shaw-openpgp-hkp-00#section-3) -->
<data android:pathPattern="/pks/lookup.*"/> <data android:pathPattern="/pks/lookup.*" />
</intent-filter> </intent-filter>
<!-- Keychain's own Actions -->
<!-- IMPORT_KEY with files TODO: does this work? --> <!-- IMPORT_KEY with files TODO: does this work? -->
<intent-filter android:label="@string/intent_import_key"> <intent-filter android:label="@string/intent_import_key">
<action android:name="org.sufficientlysecure.keychain.action.IMPORT_KEY" /> <action android:name="org.sufficientlysecure.keychain.action.IMPORT_KEY" />
@@ -693,9 +694,9 @@
--> -->
<activity <activity
android:name=".ui.NfcOperationActivity" android:name=".ui.NfcOperationActivity"
android:allowTaskReparenting="true"
android:launchMode="singleTop" android:launchMode="singleTop"
android:taskAffinity=":Nfc" android:taskAffinity=":Nfc" />
android:allowTaskReparenting="true" />
<!--<activity--> <!--<activity-->
<!--android:name=".ui.NfcIntentActivity"--> <!--android:name=".ui.NfcIntentActivity"-->
@@ -744,6 +745,11 @@
android:exported="false" android:exported="false"
android:label="@string/app_name" android:label="@string/app_name"
android:launchMode="singleTop" /> android:launchMode="singleTop" />
<activity
android:name=".remote.ui.SelectAllowedKeysActivity"
android:exported="false"
android:label="@string/app_name"
android:launchMode="singleTop" />
<activity <activity
android:name=".remote.ui.AppSettingsActivity" android:name=".remote.ui.AppSettingsActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
@@ -804,8 +810,8 @@
<provider <provider
android:name=".provider.TemporaryStorageProvider" android:name=".provider.TemporaryStorageProvider"
android:authorities="org.sufficientlysecure.keychain.tempstorage" android:authorities="org.sufficientlysecure.keychain.tempstorage"
android:writePermission="org.sufficientlysecure.keychain.WRITE_TEMPORARY_STORAGE" android:exported="true"
android:exported="true" /> android:writePermission="org.sufficientlysecure.keychain.WRITE_TEMPORARY_STORAGE" />
</application> </application>

View File

@@ -32,10 +32,10 @@ import java.util.regex.Pattern;
public class TwitterResource extends LinkedTokenResource { public class TwitterResource extends LinkedTokenResource {
public static final String[] CERT_PINS = new String[] { public static final String[] CERT_PINS = null; /*(new String[] {
// antec Class 3 Secure Server CA - G4 // Symantec Class 3 Secure Server CA - G4
"513fb9743870b73440418d30930699ff" "513fb9743870b73440418d30930699ff"
}; };*/
final String mHandle; final String mHandle;
final String mTweetId; final String mTweetId;

View File

@@ -376,6 +376,8 @@ public class ImportExportOperation extends BaseOperation {
log.add(LogType.MSG_IMPORT_ERROR, 1); log.add(LogType.MSG_IMPORT_ERROR, 1);
} }
ContactSyncAdapterService.requestSync();
return new ImportKeyResult(resultType, log, newKeys, updatedKeys, badKeys, secret, return new ImportKeyResult(resultType, log, newKeys, updatedKeys, badKeys, secret,
importedMasterKeyIdsArray); importedMasterKeyIdsArray);
} }

View File

@@ -1,3 +1,21 @@
/*
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.pgp; package org.sufficientlysecure.keychain.pgp;
@@ -32,7 +50,7 @@ public class PgpCertifyOperation {
OperationLog log, OperationLog log,
int indent, int indent,
CertifyAction action, CertifyAction action,
Map<ByteBuffer,byte[]> signedHashes, Map<ByteBuffer, byte[]> signedHashes,
Date creationTimestamp) { Date creationTimestamp) {
if (!secretKey.isMasterKey()) { if (!secretKey.isMasterKey()) {

View File

@@ -178,13 +178,20 @@ public class PgpSignEncryptOperation extends BaseOperation {
case PIN: case PIN:
case PATTERN: case PATTERN:
case PASSPHRASE: { 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); log.add(LogType.MSG_PSE_PENDING_PASSPHRASE, indent + 1);
return new PgpSignEncryptResult(log, RequiredInputParcel.createRequiredSignPassphrase( return new PgpSignEncryptResult(log, RequiredInputParcel.createRequiredSignPassphrase(
signingKeyRing.getMasterKeyId(), signingKey.getKeyId(), signingKeyRing.getMasterKeyId(), signingKey.getKeyId(),
cryptoInput.getSignatureTime())); cryptoInput.getSignatureTime()));
} }
if (!signingKey.unlock(cryptoInput.getPassphrase())) { if (!signingKey.unlock(localPassphrase)) {
log.add(LogType.MSG_PSE_ERROR_BAD_PASSPHRASE, indent); log.add(LogType.MSG_PSE_ERROR_BAD_PASSPHRASE, indent);
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
} }

View File

@@ -73,7 +73,7 @@ public class KeychainContract {
interface ApiAppsColumns { interface ApiAppsColumns {
String PACKAGE_NAME = "package_name"; String PACKAGE_NAME = "package_name";
String PACKAGE_SIGNATURE = "package_signature"; String PACKAGE_CERTIFICATE = "package_signature";
} }
interface ApiAppsAccountsColumns { interface ApiAppsAccountsColumns {

View File

@@ -148,7 +148,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
"CREATE TABLE IF NOT EXISTS " + Tables.API_APPS + " (" "CREATE TABLE IF NOT EXISTS " + Tables.API_APPS + " ("
+ BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ ApiAppsColumns.PACKAGE_NAME + " TEXT NOT NULL UNIQUE, " + ApiAppsColumns.PACKAGE_NAME + " TEXT NOT NULL UNIQUE, "
+ ApiAppsColumns.PACKAGE_SIGNATURE + " BLOB" + ApiAppsColumns.PACKAGE_CERTIFICATE + " BLOB"
+ ")"; + ")";
private static final String CREATE_API_APPS_ACCOUNTS = private static final String CREATE_API_APPS_ACCOUNTS =

View File

@@ -50,7 +50,6 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.PgpConstants; import org.sufficientlysecure.keychain.pgp.PgpConstants;
import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey; import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
@@ -1415,7 +1414,7 @@ public class ProviderHelper {
private ContentValues contentValueForApiApps(AppSettings appSettings) { private ContentValues contentValueForApiApps(AppSettings appSettings) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName()); values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName());
values.put(ApiApps.PACKAGE_SIGNATURE, appSettings.getPackageSignature()); values.put(ApiApps.PACKAGE_CERTIFICATE, appSettings.getPackageSignature());
return values; return values;
} }
@@ -1462,7 +1461,7 @@ public class ProviderHelper {
settings.setPackageName(cursor.getString( settings.setPackageName(cursor.getString(
cursor.getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME))); cursor.getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME)));
settings.setPackageSignature(cursor.getBlob( settings.setPackageSignature(cursor.getBlob(
cursor.getColumnIndex(KeychainContract.ApiApps.PACKAGE_SIGNATURE))); cursor.getColumnIndex(KeychainContract.ApiApps.PACKAGE_CERTIFICATE)));
} }
} finally { } finally {
if (cursor != null) { if (cursor != null) {
@@ -1554,31 +1553,10 @@ public class ProviderHelper {
mContentResolver.insert(uri, values); mContentResolver.insert(uri, values);
} }
public Set<String> getAllFingerprints(Uri uri) { public byte[] getApiAppCertificate(String packageName) {
Set<String> 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) {
Uri queryUri = ApiApps.buildByPackageNameUri(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); Cursor cursor = mContentResolver.query(queryUri, projection, null, null, null);
try { try {

View File

@@ -31,10 +31,12 @@ import android.provider.OpenableColumns;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.DatabaseUtil; import org.sufficientlysecure.keychain.util.DatabaseUtil;
import org.sufficientlysecure.keychain.util.Log;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.util.UUID;
public class TemporaryStorageProvider extends ContentProvider { 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_NAME = "name";
private static final String COLUMN_TIME = "time"; private static final String COLUMN_TIME = "time";
private static final Uri BASE_URI = Uri.parse("content://org.sufficientlysecure.keychain.tempstorage/"); 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) { public static Uri createFile(Context context, String targetName) {
ContentValues contentValues = new ContentValues(); ContentValues contentValues = new ContentValues();
@@ -66,7 +70,7 @@ public class TemporaryStorageProvider extends ContentProvider {
@Override @Override
public void onCreate(SQLiteDatabase db) { public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_FILES + " (" + db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_FILES + " (" +
COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + COLUMN_ID + " TEXT PRIMARY KEY, " +
COLUMN_NAME + " TEXT, " + COLUMN_NAME + " TEXT, " +
COLUMN_TIME + " INTEGER" + COLUMN_TIME + " INTEGER" +
");"); ");");
@@ -74,28 +78,39 @@ public class TemporaryStorageProvider extends ContentProvider {
@Override @Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 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 { private File getFile(Uri uri) throws FileNotFoundException {
try { try {
return getFile(Integer.parseInt(uri.getLastPathSegment())); return getFile(uri.getLastPathSegment());
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
throw new FileNotFoundException(); throw new FileNotFoundException();
} }
} }
private File getFile(int id) { private File getFile(String id) {
return new File(getContext().getCacheDir(), "temp/" + id); return new File(cacheDir, "temp/" + id);
} }
@Override @Override
public boolean onCreate() { public boolean onCreate() {
db = new TemporaryStorageDatabase(getContext()); db = new TemporaryStorageDatabase(getContext());
return new File(getContext().getCacheDir(), "temp").mkdirs(); cacheDir = getContext().getCacheDir();
return new File(cacheDir, "temp").mkdirs();
} }
@Override @Override
@@ -133,13 +148,15 @@ public class TemporaryStorageProvider extends ContentProvider {
if (!values.containsKey(COLUMN_TIME)) { if (!values.containsKey(COLUMN_TIME)) {
values.put(COLUMN_TIME, System.currentTimeMillis()); 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); int insert = (int) db.getWritableDatabase().insert(TABLE_FILES, null, values);
try { try {
getFile(insert).createNewFile(); getFile(uuid).createNewFile();
} catch (IOException e) { } catch (IOException e) {
return null; return null;
} }
return Uri.withAppendedPath(BASE_URI, Long.toString(insert)); return Uri.withAppendedPath(BASE_URI, uuid);
} }
@Override @Override
@@ -152,7 +169,7 @@ public class TemporaryStorageProvider extends ContentProvider {
selectionArgs, null, null, null); selectionArgs, null, null, null);
if (files != null) { if (files != null) {
while (files.moveToNext()) { while (files.moveToNext()) {
getFile(files.getInt(0)).delete(); getFile(files.getString(0)).delete();
} }
files.close(); files.close();
return db.getWritableDatabase().delete(TABLE_FILES, selection, selectionArgs); return db.getWritableDatabase().delete(TABLE_FILES, selection, selectionArgs);

View File

@@ -34,6 +34,7 @@ import org.openintents.openpgp.util.OpenPgpApi;
import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; 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.OperationResult.LogEntryParcel;
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult; import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
import org.sufficientlysecure.keychain.pgp.PgpConstants; 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.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity; 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.remote.ui.SelectSignKeyIdActivity;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
@@ -205,6 +207,18 @@ public class OpenPgpService extends RemoteService {
PendingIntent.FLAG_CANCEL_CURRENT); 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) { private PendingIntent getShowKeyPendingIntent(long masterKeyId) {
Intent intent = new Intent(getBaseContext(), ViewKeyActivity.class); Intent intent = new Intent(getBaseContext(), ViewKeyActivity.class);
intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
@@ -403,6 +417,20 @@ public class OpenPgpService extends RemoteService {
.setAdditionalEncryptId(signKeyId); // add sign key for encryption .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); CryptoInputParcel inputParcel = CryptoInputParcelCacheService.getCryptoInputParcel(this, data);
if (inputParcel == null) { if (inputParcel == null) {
inputParcel = new CryptoInputParcel(); inputParcel = new CryptoInputParcel();
@@ -476,13 +504,12 @@ public class OpenPgpService extends RemoteService {
} }
String currentPkg = getCurrentCallingPackage(); String currentPkg = getCurrentCallingPackage();
Set<Long> allowedKeyIds; Set<Long> allowedKeyIds = mProviderHelper.getAllowedKeyIdsForApp(
KeychainContract.ApiAllowedKeys.buildBaseUri(currentPkg));
if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) < 7) { if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) < 7) {
allowedKeyIds = mProviderHelper.getAllKeyIdsForApp( allowedKeyIds.addAll(mProviderHelper.getAllKeyIdsForApp(
ApiAccounts.buildBaseUri(currentPkg)); ApiAccounts.buildBaseUri(currentPkg)));
} else {
allowedKeyIds = mProviderHelper.getAllowedKeyIdsForApp(
KeychainContract.ApiAllowedKeys.buildBaseUri(currentPkg));
} }
long inputLength = is.available(); long inputLength = is.available();
@@ -575,6 +602,15 @@ public class OpenPgpService extends RemoteService {
return result; return result;
} else { } else {
LogEntryParcel errorMsg = pgpResult.getLog().getLast(); 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())); throw new Exception(getString(errorMsg.mType.getMsgId()));
} }

View File

@@ -37,6 +37,8 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity; import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -45,10 +47,10 @@ import java.util.Arrays;
*/ */
public abstract class RemoteService extends Service { public abstract class RemoteService extends Service {
public static class WrongPackageSignatureException extends Exception { public static class WrongPackageCertificateException extends Exception {
private static final long serialVersionUID = -8294642703122196028L; private static final long serialVersionUID = -8294642703122196028L;
public WrongPackageSignatureException(String message) { public WrongPackageCertificateException(String message) {
super(message); super(message);
} }
} }
@@ -74,9 +76,9 @@ public abstract class RemoteService extends Service {
String packageName = getCurrentCallingPackage(); String packageName = getCurrentCallingPackage();
Log.d(Constants.TAG, "isAllowed packageName: " + packageName); Log.d(Constants.TAG, "isAllowed packageName: " + packageName);
byte[] packageSignature; byte[] packageCertificate;
try { try {
packageSignature = getPackageSignature(packageName); packageCertificate = getPackageCertificate(packageName);
} catch (NameNotFoundException e) { } catch (NameNotFoundException e) {
Log.e(Constants.TAG, "Should not happen, returning!", e); Log.e(Constants.TAG, "Should not happen, returning!", e);
// return error // return error
@@ -91,7 +93,7 @@ public abstract class RemoteService extends Service {
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class); Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
intent.setAction(RemoteServiceActivity.ACTION_REGISTER); intent.setAction(RemoteServiceActivity.ACTION_REGISTER);
intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_NAME, packageName); 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); intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0, PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
@@ -105,7 +107,7 @@ public abstract class RemoteService extends Service {
return result; return result;
} }
} catch (WrongPackageSignatureException e) { } catch (WrongPackageCertificateException e) {
Log.e(Constants.TAG, "wrong signature!", e); Log.e(Constants.TAG, "wrong signature!", e);
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class); 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, PackageInfo pkgInfo = getPackageManager().getPackageInfo(packageName,
PackageManager.GET_SIGNATURES); PackageManager.GET_SIGNATURES);
Signature[] signatures = pkgInfo.signatures; // NOTE: Silly Android API naming: Signatures are actually certificates
// TODO: Only first signature?! Signature[] certificates = pkgInfo.signatures;
byte[] packageSignature = signatures[0].toByteArray(); 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 * @return package name
*/ */
protected String getCurrentCallingPackage() { protected String getCurrentCallingPackage() {
// TODO:
// callingPackages contains more than one entry when sharedUserId has been used...
String[] callingPackages = getPackageManager().getPackagesForUid(Binder.getCallingUid()); 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]; String currentPkg = callingPackages[0];
Log.d(Constants.TAG, "currentPkg: " + currentPkg); Log.d(Constants.TAG, "currentPkg: " + currentPkg);
@@ -155,12 +170,12 @@ public abstract class RemoteService extends Service {
/** /**
* DEPRECATED API * DEPRECATED API
* * <p/>
* Retrieves AccountSettings from database for the application calling this remote service * Retrieves AccountSettings from database for the application calling this remote service
*/ */
protected AccountSettings getAccSettings(String accountName) { protected AccountSettings getAccSettings(String accountName) {
String currentPkg = getCurrentCallingPackage(); String currentPkg = getCurrentCallingPackage();
Log.d(Constants.TAG, "getAccSettings accountName: "+ accountName); Log.d(Constants.TAG, "getAccSettings accountName: " + accountName);
Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(currentPkg, 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 * @param allowOnlySelf allow only Keychain app itself
* @return true if process is allowed to use this service * @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); return isUidAllowed(Binder.getCallingUid(), allowOnlySelf);
} }
private boolean isUidAllowed(int uid, boolean allowOnlySelf) private boolean isUidAllowed(int uid, boolean allowOnlySelf)
throws WrongPackageSignatureException { throws WrongPackageCertificateException {
if (android.os.Process.myUid() == uid) { if (android.os.Process.myUid() == uid) {
return true; 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! * Checks if packageName is a registered app for the API. Does not return true for own package!
* *
* @param packageName * @throws WrongPackageCertificateException
* @return
* @throws WrongPackageSignatureException
*/ */
private boolean isPackageAllowed(String packageName) throws WrongPackageSignatureException { private boolean isPackageAllowed(String packageName) throws WrongPackageCertificateException {
Log.d(Constants.TAG, "isPackageAllowed packageName: " + packageName); Log.d(Constants.TAG, "isPackageAllowed packageName: " + packageName);
ArrayList<String> allowedPkgs = mProviderHelper.getRegisteredApiApps(); ArrayList<String> allowedPkgs = mProviderHelper.getRegisteredApiApps();
@@ -244,22 +257,22 @@ public abstract class RemoteService extends Service {
Log.d(Constants.TAG, "Package is allowed! packageName: " + packageName); Log.d(Constants.TAG, "Package is allowed! packageName: " + packageName);
// check package signature // check package signature
byte[] currentSig; byte[] currentCert;
try { try {
currentSig = getPackageSignature(packageName); currentCert = getPackageCertificate(packageName);
} catch (NameNotFoundException e) { } catch (NameNotFoundException e) {
throw new WrongPackageSignatureException(e.getMessage()); throw new WrongPackageCertificateException(e.getMessage());
} }
byte[] storedSig = mProviderHelper.getApiAppSignature(packageName); byte[] storedCert = mProviderHelper.getApiAppCertificate(packageName);
if (Arrays.equals(currentSig, storedSig)) { if (Arrays.equals(currentCert, storedCert)) {
Log.d(Constants.TAG, Log.d(Constants.TAG,
"Package signature is correct! (equals signature from database)"); "Package certificate is correct! (equals certificate from database)");
return true; return true;
} else { } else {
throw new WrongPackageSignatureException( throw new WrongPackageCertificateException(
"PACKAGE NOT ALLOWED! Signature wrong! (Signature not " + "PACKAGE NOT ALLOWED! Certificate wrong! (Certificate not " +
"equals signature from database)"); "equals certificate from database)");
} }
} }

View File

@@ -45,6 +45,7 @@ import org.sufficientlysecure.keychain.util.Log;
import java.util.ArrayList; import java.util.ArrayList;
// TODO: make extensible BaseRemoteServiceActivity and extend these cases from it
public class RemoteServiceActivity extends BaseActivity { public class RemoteServiceActivity extends BaseActivity {
public static final String ACTION_REGISTER = Constants.INTENT_PREFIX + "API_ACTIVITY_REGISTER"; public static final String ACTION_REGISTER = Constants.INTENT_PREFIX + "API_ACTIVITY_REGISTER";

View File

@@ -0,0 +1,115 @@
/*
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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();
}
}

View File

@@ -50,18 +50,18 @@ import java.util.concurrent.atomic.AtomicBoolean;
*/ */
public class CloudImportService extends Service implements Progressable { 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_MESSENGER = "messenger";
public static final String EXTRA_DATA = "data"; 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_LIST = "import_key_list";
public static final String IMPORT_KEY_SERVER = "import_key_server"; public static final String IMPORT_KEY_SERVER = "import_key_server";
// indicates a request to cancel the import // indicates a request to cancel the import
public static final String ACTION_CANCEL = Constants.INTENT_PREFIX + "CANCEL"; 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); private static AtomicBoolean mActionCancelled = new AtomicBoolean(false);
@Override @Override
@@ -86,7 +86,7 @@ public class CloudImportService extends Service implements Progressable {
public KeyImportAccumulator(int totalKeys) { public KeyImportAccumulator(int totalKeys) {
mTotalKeys = totalKeys; mTotalKeys = totalKeys;
//ignore updates from ImportExportOperation for now // ignore updates from ImportExportOperation for now
mImportProgressable = new Progressable() { mImportProgressable = new Progressable() {
@Override @Override
public void setProgress(String message, int current, int total) { public void setProgress(String message, int current, int total) {
@@ -131,20 +131,17 @@ public class CloudImportService extends Service implements Progressable {
mSecret += result.mSecret; mSecret += result.mSecret;
long[] masterKeyIds = result.getImportedMasterKeyIds(); long[] masterKeyIds = result.getImportedMasterKeyIds();
for (int i = 0; i < masterKeyIds.length; i++) { for (long masterKeyId : masterKeyIds) {
mImportedMasterKeyIds.add(masterKeyIds[i]); mImportedMasterKeyIds.add(masterKeyId);
} }
// if any key import has been cancelled, set result type to cancelled // if any key import has been cancelled, set result type to cancelled
// resultType is added to in getConsolidatedKayImport to account for remaining factors // resultType is added to in getConsolidatedKayImport to account for remaining factors
mResultType |= result.getResult() & ImportKeyResult.RESULT_CANCELLED; mResultType |= result.getResult() & ImportKeyResult.RESULT_CANCELLED;
} }
/** /**
* returns accumulated result of all imports so far * returns accumulated result of all imports so far
*
* @return
*/ */
public ImportKeyResult getConsolidatedImportKeyResult() { public ImportKeyResult getConsolidatedImportKeyResult() {
@@ -205,7 +202,7 @@ public class CloudImportService extends Service implements Progressable {
Bundle data = extras.getBundle(EXTRA_DATA); Bundle data = extras.getBundle(EXTRA_DATA);
final String keyServer = data.getString(IMPORT_KEY_SERVER); 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<ParcelableKeyRing> keyList = data.getParcelableArrayList(IMPORT_KEY_LIST); final ArrayList<ParcelableKeyRing> keyList = data.getParcelableArrayList(IMPORT_KEY_LIST);
// Adding keys to the ThreadPoolExecutor takes time, we don't want to block the main thread // 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"); new ParcelableFileCache<>(this, "key_import.pcl");
int totKeys = 0; int totKeys = 0;
Iterator<ParcelableKeyRing> keyListIterator = null; Iterator<ParcelableKeyRing> 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 if (keyList == null) {//export from cache, copied from ImportExportOperation.importKeyRings
try { try {

View File

@@ -20,7 +20,6 @@ package org.sufficientlysecure.keychain.ui;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction; import android.support.v4.app.FragmentTransaction;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
@@ -84,7 +83,7 @@ public class CreateKeyActivity extends BaseNfcActivity {
String nfcUserId = intent.getStringExtra(EXTRA_NFC_USER_ID); String nfcUserId = intent.getStringExtra(EXTRA_NFC_USER_ID);
byte[] nfcAid = intent.getByteArrayExtra(EXTRA_NFC_AID); byte[] nfcAid = intent.getByteArrayExtra(EXTRA_NFC_AID);
Fragment frag2 = CreateKeyYubiImportFragment.createInstance( Fragment frag2 = CreateKeyYubiKeyImportFragment.createInstance(
nfcFingerprints, nfcAid, nfcUserId); nfcFingerprints, nfcAid, nfcUserId);
loadFragment(frag2, FragAction.START); loadFragment(frag2, FragAction.START);
@@ -99,7 +98,7 @@ public class CreateKeyActivity extends BaseNfcActivity {
if (mFirstTime) { if (mFirstTime) {
setTitle(R.string.app_name); setTitle(R.string.app_name);
setActionBarIcon(R.drawable.ic_launcher); mToolbar.setNavigationIcon(null);
mToolbar.setNavigationOnClickListener(null); mToolbar.setNavigationOnClickListener(null);
} else { } else {
setTitle(R.string.title_manage_my_keys); setTitle(R.string.title_manage_my_keys);
@@ -131,7 +130,7 @@ public class CreateKeyActivity extends BaseNfcActivity {
finish(); finish();
} catch (PgpKeyNotFoundException e) { } catch (PgpKeyNotFoundException e) {
Fragment frag = CreateKeyYubiImportFragment.createInstance( Fragment frag = CreateKeyYubiKeyImportFragment.createInstance(
scannedFingerprints, nfcAid, userId); scannedFingerprints, nfcAid, userId);
loadFragment(frag, FragAction.TO_RIGHT); loadFragment(frag, FragAction.TO_RIGHT);
} }

View File

@@ -18,7 +18,6 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import android.app.Activity; import android.app.Activity;
import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Message; import android.os.Message;
@@ -44,18 +43,17 @@ import org.sufficientlysecure.keychain.ui.widget.EmailEditText;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.regex.Pattern;
public class CreateKeyEmailFragment extends Fragment { public class CreateKeyEmailFragment extends Fragment {
private CreateKeyActivity mCreateKeyActivity;
private EmailEditText mEmailEdit;
private ArrayList<EmailAdapter.ViewModel> mAdditionalEmailModels = new ArrayList<>();
private EmailAdapter mEmailAdapter;
CreateKeyActivity mCreateKeyActivity; // NOTE: Do not use more complicated pattern like defined in android.util.Patterns.EMAIL_ADDRESS
EmailEditText mEmailEdit; // EMAIL_ADDRESS fails for mails with umlauts for example
RecyclerView mEmailsRecyclerView; private static final Pattern EMAIL_PATTERN = Pattern.compile("^[\\S]+@[\\S]+\\.[a-z]+$");
View mBackButton;
View mNextButton;
ArrayList<EmailAdapter.ViewModel> mAdditionalEmailModels;
EmailAdapter mEmailAdapter;
/** /**
* Creates new instance of this fragment * 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 * Checks if text of given EditText is not empty. If it is empty an error is
* set and the EditText gets the focus. * set and the EditText gets the focus.
* *
* @param context
* @param editText * @param editText
* @return true if EditText is not empty * @return true if EditText is not empty
*/ */
private static boolean isEditTextNotEmpty(Context context, EditText editText) { private boolean isMainEmailValid(EditText editText) {
boolean output = true; boolean output = true;
if (editText.getText().length() == 0) { if (!checkEmail(editText.getText().toString(), false)) {
editText.setError(context.getString(R.string.create_key_empty)); editText.setError(getString(R.string.create_key_empty));
editText.requestFocus(); editText.requestFocus();
output = false; output = false;
} else { } else {
@@ -95,9 +92,9 @@ public class CreateKeyEmailFragment extends Fragment {
View view = inflater.inflate(R.layout.create_key_email_fragment, container, false); View view = inflater.inflate(R.layout.create_key_email_fragment, container, false);
mEmailEdit = (EmailEditText) view.findViewById(R.id.create_key_email); mEmailEdit = (EmailEditText) view.findViewById(R.id.create_key_email);
mBackButton = view.findViewById(R.id.create_key_back_button); View backButton = view.findViewById(R.id.create_key_back_button);
mNextButton = view.findViewById(R.id.create_key_next_button); View nextButton = view.findViewById(R.id.create_key_next_button);
mEmailsRecyclerView = (RecyclerView) view.findViewById(R.id.create_key_emails); RecyclerView emailsRecyclerView = (RecyclerView) view.findViewById(R.id.create_key_emails);
// initial values // initial values
mEmailEdit.setText(mCreateKeyActivity.mEmail); mEmailEdit.setText(mCreateKeyActivity.mEmail);
@@ -106,29 +103,21 @@ public class CreateKeyEmailFragment extends Fragment {
if (mCreateKeyActivity.mEmail == null) { if (mCreateKeyActivity.mEmail == null) {
mEmailEdit.requestFocus(); mEmailEdit.requestFocus();
} }
mBackButton.setOnClickListener(new View.OnClickListener() { backButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT);
} }
}); });
mNextButton.setOnClickListener(new View.OnClickListener() { nextButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
nextClicked(); nextClicked();
} }
}); });
mEmailsRecyclerView.setHasFixedSize(true); emailsRecyclerView.setHasFixedSize(true);
mEmailsRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); emailsRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
mEmailsRecyclerView.setItemAnimator(new DefaultItemAnimator()); emailsRecyclerView.setItemAnimator(new DefaultItemAnimator());
// initial values
if (mAdditionalEmailModels == null) {
mAdditionalEmailModels = new ArrayList<>();
if (mCreateKeyActivity.mAdditionalEmails != null) {
mEmailAdapter.addAll(mCreateKeyActivity.mAdditionalEmails);
}
}
if (mEmailAdapter == null) { if (mEmailAdapter == null) {
mEmailAdapter = new EmailAdapter(mAdditionalEmailModels, new View.OnClickListener() { mEmailAdapter = new EmailAdapter(mAdditionalEmailModels, new View.OnClickListener() {
@@ -137,13 +126,77 @@ public class CreateKeyEmailFragment extends Fragment {
addEmail(); addEmail();
} }
}); });
if (mCreateKeyActivity.mAdditionalEmails != null) {
mEmailAdapter.addAll(mCreateKeyActivity.mAdditionalEmails);
}
} }
mEmailsRecyclerView.setAdapter(mEmailAdapter); emailsRecyclerView.setAdapter(mEmailAdapter);
return view; 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() { private void addEmail() {
Handler returnHandler = new Handler() { Handler returnHandler = new Handler() {
@Override @Override
@@ -153,34 +206,17 @@ public class CreateKeyEmailFragment extends Fragment {
String email = data.getString(AddEmailDialogFragment.MESSAGE_DATA_EMAIL); String email = data.getString(AddEmailDialogFragment.MESSAGE_DATA_EMAIL);
if (email.length() > 0 && mEmailEdit.getText().length() > 0 && if (checkEmail(email, true)) {
email.equals(mEmailEdit.getText().toString())) { // add new user id
Notify.create(getActivity(), mEmailAdapter.add(email);
getString(R.string.create_key_email_already_exists_text),
Notify.LENGTH_LONG, Notify.Style.ERROR).show();
return;
} }
//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 // Create a new Messenger for the communication back
Messenger messenger = new Messenger(returnHandler); Messenger messenger = new Messenger(returnHandler);
AddEmailDialogFragment addEmailDialog = AddEmailDialogFragment.newInstance(messenger); AddEmailDialogFragment addEmailDialog = AddEmailDialogFragment.newInstance(messenger);
addEmailDialog.show(getActivity().getSupportFragmentManager(), "addEmailDialog"); addEmailDialog.show(getActivity().getSupportFragmentManager(), "addEmailDialog");
} }
@@ -191,7 +227,7 @@ public class CreateKeyEmailFragment extends Fragment {
} }
private void nextClicked() { private void nextClicked() {
if (isEditTextNotEmpty(getActivity(), mEmailEdit)) { if (isMainEmailValid(mEmailEdit)) {
// save state // save state
mCreateKeyActivity.mEmail = mEmailEdit.getText().toString(); mCreateKeyActivity.mEmail = mEmailEdit.getText().toString();
mCreateKeyActivity.mAdditionalEmails = getAdditionalEmails(); mCreateKeyActivity.mAdditionalEmails = getAdditionalEmails();

View File

@@ -21,7 +21,6 @@ import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.text.Editable;
import android.text.method.HideReturnsTransformationMethod; import android.text.method.HideReturnsTransformationMethod;
import android.text.method.PasswordTransformationMethod; import android.text.method.PasswordTransformationMethod;
import android.view.LayoutInflater; 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.ui.widget.PassphraseEditText;
import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Passphrase;
import java.util.ArrayList;
import java.util.Arrays;
public class CreateKeyPassphraseFragment extends Fragment { public class CreateKeyPassphraseFragment extends Fragment {
// view // view
@@ -111,8 +107,8 @@ public class CreateKeyPassphraseFragment extends Fragment {
// initial values // initial values
// TODO: using String here is unsafe... // TODO: using String here is unsafe...
if (mCreateKeyActivity.mPassphrase != null) { if (mCreateKeyActivity.mPassphrase != null) {
mPassphraseEdit.setText(Arrays.toString(mCreateKeyActivity.mPassphrase.getCharArray())); mPassphraseEdit.setText(new String(mCreateKeyActivity.mPassphrase.getCharArray()));
mPassphraseEditAgain.setText(Arrays.toString(mCreateKeyActivity.mPassphrase.getCharArray())); mPassphraseEditAgain.setText(new String(mCreateKeyActivity.mPassphrase.getCharArray()));
} }
mPassphraseEdit.requestFocus(); mPassphraseEdit.requestFocus();

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> * Copyright (C) 2014-2015 Dominik Schürmann <dominik@dominikschuermann.de>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@@ -18,37 +18,20 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import android.app.Activity; import android.app.Activity;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; 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.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.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.TextView; import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; 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.Log;
import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.Preferences;
import java.util.ArrayList;
import java.util.List;
public class CreateKeyStartFragment extends Fragment { public class CreateKeyStartFragment extends Fragment {
CreateKeyActivity mCreateKeyActivity; CreateKeyActivity mCreateKeyActivity;
@@ -56,8 +39,8 @@ public class CreateKeyStartFragment extends Fragment {
View mCreateKey; View mCreateKey;
View mImportKey; View mImportKey;
View mYubiKey; View mYubiKey;
TextView mCancel; TextView mSkipOrCancel;
public static final int REQUEST_CODE_CREATE_OR_IMPORT_KEY = 0x00007012; public static final int REQUEST_CODE_IMPORT_KEY = 0x00007012;
/** /**
* Creates new instance of this fragment * 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); mCreateKey = view.findViewById(R.id.create_key_create_key_button);
mImportKey = view.findViewById(R.id.create_key_import_button); mImportKey = view.findViewById(R.id.create_key_import_button);
mYubiKey = view.findViewById(R.id.create_key_yubikey_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) { if (mCreateKeyActivity.mFirstTime) {
mCancel.setText(R.string.first_time_skip); mSkipOrCancel.setText(R.string.first_time_skip);
} else { } else {
mCancel.setText(R.string.btn_do_not_save); mSkipOrCancel.setText(R.string.btn_do_not_save);
} }
mCreateKey.setOnClickListener(new View.OnClickListener() { mCreateKey.setOnClickListener(new View.OnClickListener() {
@@ -98,7 +81,7 @@ public class CreateKeyStartFragment extends Fragment {
mYubiKey.setOnClickListener(new View.OnClickListener() { mYubiKey.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
CreateKeyYubiWaitFragment frag = new CreateKeyYubiWaitFragment(); CreateKeyYubiKeyWaitFragment frag = new CreateKeyYubiKeyWaitFragment();
mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT);
} }
}); });
@@ -108,48 +91,48 @@ public class CreateKeyStartFragment extends Fragment {
public void onClick(View v) { public void onClick(View v) {
Intent intent = new Intent(mCreateKeyActivity, ImportKeysActivity.class); Intent intent = new Intent(mCreateKeyActivity, ImportKeysActivity.class);
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN); 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 @Override
public void onClick(View v) { 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; 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 @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) { public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, 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) { 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 { } else {
Log.e(Constants.TAG, "No valid request code!"); Log.e(Constants.TAG, "No valid request code!");

View File

@@ -48,7 +48,7 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Preferences; 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"; private static final String ARG_FINGERPRINT = "fingerprint";
public static final String ARG_AID = "aid"; 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) { public static Fragment createInstance(byte[] scannedFingerprints, byte[] nfcAid, String userId) {
CreateKeyYubiImportFragment frag = new CreateKeyYubiImportFragment(); CreateKeyYubiKeyImportFragment frag = new CreateKeyYubiKeyImportFragment();
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putByteArray(ARG_FINGERPRINT, scannedFingerprints); args.putByteArray(ARG_FINGERPRINT, scannedFingerprints);

View File

@@ -28,7 +28,7 @@ import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
public class CreateKeyYubiWaitFragment extends Fragment { public class CreateKeyYubiKeyWaitFragment extends Fragment {
CreateKeyActivity mCreateKeyActivity; CreateKeyActivity mCreateKeyActivity;
View mBackButton; View mBackButton;

View File

@@ -25,7 +25,7 @@ import android.view.View;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; 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.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;

View File

@@ -86,12 +86,12 @@ public class DecryptFilesFragment extends DecryptFragment {
*/ */
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 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); mFilename = (TextView) view.findViewById(R.id.decrypt_files_filename);
mDeleteAfter = (CheckBox) view.findViewById(R.id.decrypt_file_delete_after_decryption); mDeleteAfter = (CheckBox) view.findViewById(R.id.decrypt_files_delete_after_decryption);
mDecryptButton = view.findViewById(R.id.decrypt_file_action_decrypt); mDecryptButton = view.findViewById(R.id.decrypt_files_action_decrypt);
view.findViewById(R.id.decrypt_file_browse).setOnClickListener(new View.OnClickListener() { view.findViewById(R.id.decrypt_files_browse).setOnClickListener(new View.OnClickListener() {
public void onClick(View v) { public void onClick(View v) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
FileHelper.openDocument(DecryptFilesFragment.this, "*/*", REQUEST_CODE_INPUT); FileHelper.openDocument(DecryptFilesFragment.this, "*/*", REQUEST_CODE_INPUT);
@@ -232,7 +232,6 @@ public class DecryptFilesFragment extends DecryptFragment {
returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT); returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
if (pgpResult.success()) { if (pgpResult.success()) {
switch (mCurrentCryptoOperation) { switch (mCurrentCryptoOperation) {
case KeychainIntentService.ACTION_DECRYPT_METADATA: { case KeychainIntentService.ACTION_DECRYPT_METADATA: {
askForOutputFilename(pgpResult.getDecryptMetadata().getFilename()); askForOutputFilename(pgpResult.getDecryptMetadata().getFilename());
@@ -264,9 +263,8 @@ public class DecryptFilesFragment extends DecryptFragment {
break; break;
} }
} }
} else {
pgpResult.createNotify(getActivity()).show();
} }
pgpResult.createNotify(getActivity()).show(DecryptFilesFragment.this);
} }
} }
@@ -309,7 +307,7 @@ public class DecryptFilesFragment extends DecryptFragment {
} }
@Override @Override
protected void onVerifyLoaded(boolean verified) { protected void onVerifyLoaded(boolean hideErrorOverlay) {
} }
} }

View File

@@ -30,6 +30,7 @@ import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader; import android.support.v4.content.Loader;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.Button;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.TextView; 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.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.Preferences;
public abstract class DecryptFragment extends CryptoOperationFragment implements public abstract class DecryptFragment extends CryptoOperationFragment implements
LoaderManager.LoaderCallbacks<Cursor> { LoaderManager.LoaderCallbacks<Cursor> {
public static final int LOADER_ID_UNIFIED = 0; public static final int LOADER_ID_UNIFIED = 0;
protected LinearLayout mResultLayout; protected LinearLayout mResultLayout;
protected ImageView mEncryptionIcon; protected ImageView mEncryptionIcon;
protected TextView mEncryptionText; protected TextView mEncryptionText;
protected ImageView mSignatureIcon; protected ImageView mSignatureIcon;
protected TextView mSignatureText; protected TextView mSignatureText;
protected View mSignatureLayout; protected View mSignatureLayout;
protected TextView mSignatureName; protected TextView mSignatureName;
protected TextView mSignatureEmail; protected TextView mSignatureEmail;
protected TextView mSignatureAction; protected TextView mSignatureAction;
private LinearLayout mContentLayout;
private LinearLayout mErrorOverlayLayout;
private OpenPgpSignatureResult mSignatureResult; private OpenPgpSignatureResult mSignatureResult;
@Override @Override
@@ -82,7 +83,6 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
// NOTE: These views are inside the activity! // NOTE: These views are inside the activity!
mResultLayout = (LinearLayout) getActivity().findViewById(R.id.result_main_layout); mResultLayout = (LinearLayout) getActivity().findViewById(R.id.result_main_layout);
mResultLayout.setVisibility(View.GONE); mResultLayout.setVisibility(View.GONE);
mEncryptionIcon = (ImageView) getActivity().findViewById(R.id.result_encryption_icon); mEncryptionIcon = (ImageView) getActivity().findViewById(R.id.result_encryption_icon);
mEncryptionText = (TextView) getActivity().findViewById(R.id.result_encryption_text); mEncryptionText = (TextView) getActivity().findViewById(R.id.result_encryption_text);
mSignatureIcon = (ImageView) getActivity().findViewById(R.id.result_signature_icon); 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); mSignatureEmail = (TextView) getActivity().findViewById(R.id.result_signature_email);
mSignatureAction = (TextView) getActivity().findViewById(R.id.result_signature_action); 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) { private void lookupUnknownKey(long unknownKeyId) {
@@ -113,12 +124,9 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
final ImportKeyResult result = final ImportKeyResult result =
returnData.getParcelable(OperationResult.EXTRA_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); 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); intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
getActivity().startService(intent); getActivity().startService(intent);
} }
private void showKey(long keyId) { private void showKey(long keyId) {
@@ -191,6 +198,9 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
getLoaderManager().destroyLoader(LOADER_ID_UNIFIED); getLoaderManager().destroyLoader(LOADER_ID_UNIFIED);
mErrorOverlayLayout.setVisibility(View.GONE);
mContentLayout.setVisibility(View.VISIBLE);
onVerifyLoaded(true); onVerifyLoaded(true);
return; return;
@@ -205,7 +215,6 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
} }
getLoaderManager().restartLoader(LOADER_ID_UNIFIED, null, this); getLoaderManager().restartLoader(LOADER_ID_UNIFIED, null, this);
} }
private void setSignatureLayoutVisibility(int visibility) { private void setSignatureLayoutVisibility(int visibility) {
@@ -228,8 +237,6 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
KeychainContract.KeyRings._ID, KeychainContract.KeyRings._ID,
KeychainContract.KeyRings.MASTER_KEY_ID, KeychainContract.KeyRings.MASTER_KEY_ID,
KeychainContract.KeyRings.USER_ID, KeychainContract.KeyRings.USER_ID,
KeychainContract.KeyRings.IS_REVOKED,
KeychainContract.KeyRings.IS_EXPIRED,
KeychainContract.KeyRings.VERIFIED, KeychainContract.KeyRings.VERIFIED,
KeychainContract.KeyRings.HAS_ANY_SECRET, KeychainContract.KeyRings.HAS_ANY_SECRET,
}; };
@@ -237,10 +244,8 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
@SuppressWarnings("unused") @SuppressWarnings("unused")
static final int INDEX_MASTER_KEY_ID = 1; static final int INDEX_MASTER_KEY_ID = 1;
static final int INDEX_USER_ID = 2; static final int INDEX_USER_ID = 2;
static final int INDEX_IS_REVOKED = 3; static final int INDEX_VERIFIED = 3;
static final int INDEX_IS_EXPIRED = 4; static final int INDEX_HAS_ANY_SECRET = 4;
static final int INDEX_VERIFIED = 5;
static final int INDEX_HAS_ANY_SECRET = 6;
@Override @Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) { public Loader<Cursor> onCreateLoader(int id, Bundle args) {
@@ -282,8 +287,10 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
getActivity(), mSignatureResult.getKeyId())); getActivity(), mSignatureResult.getKeyId()));
} }
boolean isRevoked = data.getInt(INDEX_IS_REVOKED) != 0; // NOTE: Don't use revoked and expired fields from database, they don't show
boolean isExpired = data.getInt(INDEX_IS_EXPIRED) != 0; // 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 isVerified = data.getInt(INDEX_VERIFIED) > 0;
boolean isYours = data.getInt(INDEX_HAS_ANY_SECRET) != 0; boolean isYours = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
@@ -294,6 +301,9 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
setSignatureLayoutVisibility(View.VISIBLE); setSignatureLayoutVisibility(View.VISIBLE);
setShowAction(signatureKeyId); setShowAction(signatureKeyId);
mErrorOverlayLayout.setVisibility(View.VISIBLE);
mContentLayout.setVisibility(View.GONE);
onVerifyLoaded(false); onVerifyLoaded(false);
} else if (isExpired) { } else if (isExpired) {
@@ -303,6 +313,22 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
setSignatureLayoutVisibility(View.VISIBLE); setSignatureLayoutVisibility(View.VISIBLE);
setShowAction(signatureKeyId); 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); onVerifyLoaded(true);
} else if (isYours) { } else if (isYours) {
@@ -322,6 +348,9 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
setSignatureLayoutVisibility(View.VISIBLE); setSignatureLayoutVisibility(View.VISIBLE);
setShowAction(signatureKeyId); setShowAction(signatureKeyId);
mErrorOverlayLayout.setVisibility(View.GONE);
mContentLayout.setVisibility(View.VISIBLE);
onVerifyLoaded(true); onVerifyLoaded(true);
} else { } else {
@@ -331,6 +360,9 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
setSignatureLayoutVisibility(View.VISIBLE); setSignatureLayoutVisibility(View.VISIBLE);
setShowAction(signatureKeyId); setShowAction(signatureKeyId);
mErrorOverlayLayout.setVisibility(View.GONE);
mContentLayout.setVisibility(View.VISIBLE);
onVerifyLoaded(true); onVerifyLoaded(true);
} }
@@ -344,7 +376,6 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
} }
setSignatureLayoutVisibility(View.GONE); setSignatureLayoutVisibility(View.GONE);
} }
private void showUnknownKeyStatus() { private void showUnknownKeyStatus() {
@@ -388,6 +419,9 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
} }
}); });
mErrorOverlayLayout.setVisibility(View.GONE);
mContentLayout.setVisibility(View.VISIBLE);
onVerifyLoaded(true); onVerifyLoaded(true);
break; break;
@@ -399,6 +433,9 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
setSignatureLayoutVisibility(View.GONE); setSignatureLayoutVisibility(View.GONE);
mErrorOverlayLayout.setVisibility(View.VISIBLE);
mContentLayout.setVisibility(View.GONE);
onVerifyLoaded(false); onVerifyLoaded(false);
break; break;
} }
@@ -407,6 +444,6 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
} }
protected abstract void onVerifyLoaded(boolean verified); protected abstract void onVerifyLoaded(boolean hideErrorOverlay);
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> * Copyright (C) 2012-2015 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org> * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
* *
* This program is free software: you can redistribute it and/or modify * 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.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.View; import android.view.View;
import android.widget.Toast;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.api.OpenKeychainIntents;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; 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.OperationResult;
import org.sufficientlysecure.keychain.operations.results.SingletonResult; import org.sufficientlysecure.keychain.operations.results.SingletonResult;
import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@@ -138,8 +138,6 @@ public class DecryptTextActivity extends BaseActivity {
/** /**
* Handles all actions with this intent * Handles all actions with this intent
*
* @param intent
*/ */
private void handleActions(Bundle savedInstanceState, Intent intent) { private void handleActions(Bundle savedInstanceState, Intent intent) {
String action = intent.getAction(); String action = intent.getAction();
@@ -162,10 +160,14 @@ public class DecryptTextActivity extends BaseActivity {
if (sharedText != null) { if (sharedText != null) {
loadFragment(savedInstanceState, sharedText); loadFragment(savedInstanceState, sharedText);
} else { } 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 { } else {
Log.e(Constants.TAG, "ACTION_SEND received non-plaintext, this should not happen in this activity!"); 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)) { } else if (ACTION_DECRYPT_TEXT.equals(action)) {
Log.d(Constants.TAG, "ACTION_DECRYPT_TEXT"); Log.d(Constants.TAG, "ACTION_DECRYPT_TEXT");
@@ -176,7 +178,9 @@ public class DecryptTextActivity extends BaseActivity {
if (extraText != null) { if (extraText != null) {
loadFragment(savedInstanceState, extraText); loadFragment(savedInstanceState, extraText);
} else { } 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)) { } else if (ACTION_DECRYPT_FROM_CLIPBOARD.equals(action)) {
Log.d(Constants.TAG, "ACTION_DECRYPT_FROM_CLIPBOARD"); Log.d(Constants.TAG, "ACTION_DECRYPT_FROM_CLIPBOARD");
@@ -191,6 +195,7 @@ public class DecryptTextActivity extends BaseActivity {
} }
} else if (ACTION_DECRYPT_TEXT.equals(action)) { } else if (ACTION_DECRYPT_TEXT.equals(action)) {
Log.e(Constants.TAG, "Include the extra 'text' in your Intent!"); Log.e(Constants.TAG, "Include the extra 'text' in your Intent!");
Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show();
finish(); finish();
} }
} }

View File

@@ -50,8 +50,6 @@ public class DecryptTextFragment extends DecryptFragment {
public static final String ARG_CIPHERTEXT = "ciphertext"; public static final String ARG_CIPHERTEXT = "ciphertext";
// view // view
private LinearLayout mValidLayout;
private LinearLayout mInvalidLayout;
private TextView mText; private TextView mText;
// model // model
@@ -78,19 +76,8 @@ public class DecryptTextFragment extends DecryptFragment {
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.decrypt_text_fragment, container, false); 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); 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; return view;
} }
@@ -203,7 +190,6 @@ public class DecryptTextFragment extends DecryptFragment {
returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT); returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
if (pgpResult.success()) { if (pgpResult.success()) {
byte[] decryptedMessage = returnData byte[] decryptedMessage = returnData
.getByteArray(KeychainIntentService.RESULT_DECRYPTED_BYTES); .getByteArray(KeychainIntentService.RESULT_DECRYPTED_BYTES);
String displayMessage; String displayMessage;
@@ -219,15 +205,12 @@ public class DecryptTextFragment extends DecryptFragment {
} }
mText.setText(displayMessage); mText.setText(displayMessage);
pgpResult.createNotify(getActivity()).show();
// display signature result in activity // display signature result in activity
loadVerifyResult(pgpResult); loadVerifyResult(pgpResult);
} else { } else {
pgpResult.createNotify(getActivity()).show();
// TODO: show also invalid layout with different text? // TODO: show also invalid layout with different text?
} }
pgpResult.createNotify(getActivity()).show(DecryptTextFragment.this);
} }
} }
}; };
@@ -244,18 +227,8 @@ public class DecryptTextFragment extends DecryptFragment {
} }
@Override @Override
protected void onVerifyLoaded(boolean verified) { protected void onVerifyLoaded(boolean hideErrorOverlay) {
mShowMenuOptions = hideErrorOverlay;
mShowMenuOptions = verified;
getActivity().supportInvalidateOptionsMenu(); getActivity().supportInvalidateOptionsMenu();
if (verified) {
mInvalidLayout.setVisibility(View.GONE);
mValidLayout.setVisibility(View.VISIBLE);
} else {
mInvalidLayout.setVisibility(View.VISIBLE);
mValidLayout.setVisibility(View.GONE);
}
} }
} }

View File

@@ -28,7 +28,7 @@ import android.view.View;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; 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.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Passphrase;

View File

@@ -26,7 +26,7 @@ import android.view.View;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; 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.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Passphrase;

View File

@@ -30,16 +30,16 @@ import android.view.ViewGroup;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; 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.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity; import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
import org.sufficientlysecure.keychain.service.CloudImportService; import org.sufficientlysecure.keychain.service.CloudImportService;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; 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.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
@@ -346,60 +346,66 @@ public class ImportKeysActivity extends BaseNfcActivity {
mListFragment.loadNew(loaderState); 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 * Import keys with mImportData
*/ */
public void importKeys() { 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(); ImportKeysListFragment.LoaderState ls = mListFragment.getLoaderState();
if (ls instanceof ImportKeysListFragment.BytesLoaderState) { if (ls instanceof ImportKeysListFragment.BytesLoaderState) {
Log.d(Constants.TAG, "importKeys started"); 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 // 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 // fill values for this action
Bundle data = new Bundle(); Bundle data = new Bundle();
@@ -417,14 +423,14 @@ public class ImportKeysActivity extends BaseNfcActivity {
new ParcelableFileCache<>(this, "key_import.pcl"); new ParcelableFileCache<>(this, "key_import.pcl");
cache.writeCache(selectedEntries); cache.writeCache(selectedEntries);
intent.putExtra(CloudImportService.EXTRA_DATA, data); intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// Create a new Messenger for the communication back // Create a new Messenger for the communication back
Messenger messenger = new Messenger(saveHandler); Messenger messenger = new Messenger(serviceHandler);
intent.putExtra(CloudImportService.EXTRA_MESSENGER, messenger); intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
// show progress dialog // show progress dialog
saveHandler.showProgressDialog(this); serviceHandler.showProgressDialog(this);
// start service with intent // start service with intent
startService(intent); startService(intent);
@@ -436,6 +442,20 @@ public class ImportKeysActivity extends BaseNfcActivity {
} else if (ls instanceof ImportKeysListFragment.CloudLoaderState) { } else if (ls instanceof ImportKeysListFragment.CloudLoaderState) {
ImportKeysListFragment.CloudLoaderState sls = (ImportKeysListFragment.CloudLoaderState) ls; 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 // Send all information needed to service to query keys in other thread
Intent intent = new Intent(this, CloudImportService.class); Intent intent = new Intent(this, CloudImportService.class);
@@ -460,11 +480,11 @@ public class ImportKeysActivity extends BaseNfcActivity {
intent.putExtra(CloudImportService.EXTRA_DATA, data); intent.putExtra(CloudImportService.EXTRA_DATA, data);
// Create a new Messenger for the communication back // Create a new Messenger for the communication back
Messenger messenger = new Messenger(saveHandler); Messenger messenger = new Messenger(serviceHandler);
intent.putExtra(CloudImportService.EXTRA_MESSENGER, messenger); intent.putExtra(CloudImportService.EXTRA_MESSENGER, messenger);
// show progress dialog // show progress dialog
saveHandler.showProgressDialog(this); serviceHandler.showProgressDialog(this);
// start service with intent // start service with intent
startService(intent); startService(intent);

View File

@@ -37,7 +37,7 @@ import com.google.zxing.integration.android.IntentResult;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; 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.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult;

View File

@@ -70,7 +70,6 @@ import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.service.PassphraseCacheService; 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.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify;

View File

@@ -64,7 +64,7 @@ public class MainActivity extends AppCompatActivity implements FabContainer {
transaction.replace(R.id.main_fragment_container, mainFragment); transaction.replace(R.id.main_fragment_container, mainFragment);
transaction.commit(); transaction.commit();
mToolbar = (Toolbar) findViewById(R.id.activity_main_toolbar); mToolbar = (Toolbar) findViewById(R.id.toolbar);
mToolbar.setTitle(R.string.app_name); mToolbar.setTitle(R.string.app_name);
setSupportActionBar(mToolbar); setSupportActionBar(mToolbar);

View File

@@ -26,6 +26,7 @@ import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.support.v4.app.ActivityCompat; import android.support.v4.app.ActivityCompat;
import android.support.v4.app.LoaderManager; import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader; import android.support.v4.content.CursorLoader;
@@ -42,19 +43,24 @@ import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; 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.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.provider.ProviderHelper; 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.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.QrCodeUtils; import org.sufficientlysecure.keychain.ui.util.QrCodeUtils;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.NfcHelper; import org.sufficientlysecure.keychain.util.NfcHelper;
import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
import java.io.IOException; import java.io.IOException;
import java.io.FileNotFoundException;
public class ViewKeyAdvShareFragment extends LoaderFragment implements public class ViewKeyAdvShareFragment extends LoaderFragment implements
@@ -175,11 +181,11 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
boolean toClipboard) { boolean toClipboard) {
try { try {
String content; String content;
byte[] fingerprintData = (byte[]) providerHelper.getGenericData(
KeyRings.buildUnifiedKeyRingUri(dataUri),
Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
if (fingerprintOnly) { if (fingerprintOnly) {
byte[] data = (byte[]) providerHelper.getGenericData( String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintData);
KeyRings.buildUnifiedKeyRingUri(dataUri),
Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(data);
if (!toClipboard) { if (!toClipboard) {
content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint; content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
} else { } else {
@@ -213,13 +219,48 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
Intent sendIntent = new Intent(Intent.ACTION_SEND); Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, content); sendIntent.putExtra(Intent.EXTRA_TEXT, content);
sendIntent.setType("text/plain"); sendIntent.setType("text/plain");
String title; String title;
if (fingerprintOnly) { if (fingerprintOnly) {
title = getResources().getString(R.string.title_share_fingerprint_with); title = getResources().getString(R.string.title_share_fingerprint_with);
} else { } else {
title = getResources().getString(R.string.title_share_key); 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) { } catch (PgpGeneralException | IOException e) {
Log.e(Constants.TAG, "error processing key!", e); Log.e(Constants.TAG, "error processing key!", e);
@@ -379,4 +420,4 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
} }
} }

View File

@@ -202,6 +202,8 @@ public class ViewKeyFragment extends LoaderFragment implements
* In the case of a secret key, "me" (own profile) contact details are loaded. * In the case of a secret key, "me" (own profile) contact details are loaded.
*/ */
private void loadLinkedSystemContact(final long contactId) { private void loadLinkedSystemContact(final long contactId) {
// contact doesn't exist, stop
if(contactId == -1) return;
final Context context = mSystemContactName.getContext(); final Context context = mSystemContactName.getContext();
final ContentResolver resolver = context.getContentResolver(); final ContentResolver resolver = context.getContentResolver();

View File

@@ -18,15 +18,12 @@
package org.sufficientlysecure.keychain.ui.adapter; package org.sufficientlysecure.keychain.ui.adapter;
import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.TimeZone;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.graphics.PorterDuff; import android.graphics.PorterDuff;
import android.support.v4.widget.CursorAdapter; import android.support.v4.widget.CursorAdapter;
import android.text.format.DateFormat;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@@ -176,9 +173,8 @@ public class KeyAdapter extends CursorAdapter {
| DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_TIME
| DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_YEAR
| DateUtils.FORMAT_ABBREV_MONTH); | DateUtils.FORMAT_ABBREV_MONTH);
mCreationDate.setText(context.getString(R.string.label_key_created,
mCreationDate.setText(context.getString(R.string.label_creation, dateTime));
dateTime));
mCreationDate.setVisibility(View.VISIBLE); mCreationDate.setVisibility(View.VISIBLE);
} else { } else {
mCreationDate.setVisibility(View.GONE); 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());
}
} }
} }

View File

@@ -140,8 +140,7 @@ abstract public class SelectKeyCursorAdapter extends CursorAdapter {
| DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_TIME
| DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_YEAR
| DateUtils.FORMAT_ABBREV_MONTH); | DateUtils.FORMAT_ABBREV_MONTH);
h.creation.setText(context.getString(R.string.label_key_created, dateTime));
h.creation.setText(context.getString(R.string.label_creation, dateTime));
h.creation.setVisibility(View.VISIBLE); h.creation.setVisibility(View.VISIBLE);
} else { } else {
h.creation.setVisibility(View.GONE); h.creation.setVisibility(View.GONE);

View File

@@ -112,9 +112,11 @@ public class AddEmailDialogFragment extends DialogFragment implements OnEditorAc
mEmail.post(new Runnable() { mEmail.post(new Runnable() {
@Override @Override
public void run() { public void run() {
InputMethodManager imm = (InputMethodManager) getActivity() if(getActivity() != null) {
.getSystemService(Context.INPUT_METHOD_SERVICE); InputMethodManager imm = (InputMethodManager) getActivity()
imm.showSoftInput(mEmail, InputMethodManager.SHOW_IMPLICIT); .getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(mEmail, InputMethodManager.SHOW_IMPLICIT);
}
} }
}); });
} }

View File

@@ -94,12 +94,12 @@ public class ProgressDialogFragment extends DialogFragment {
/** Updates progress of dialog */ /** Updates progress of dialog */
public void setProgress(String message, int progress, int max) { public void setProgress(String message, int progress, int max) {
if (mIsCancelled) { ProgressDialog dialog = (ProgressDialog) getDialog();
if (mIsCancelled || dialog == null) {
return; return;
} }
ProgressDialog dialog = (ProgressDialog) getDialog();
dialog.setMessage(message); dialog.setMessage(message);
dialog.setProgress(progress); dialog.setProgress(progress);
dialog.setMax(max); dialog.setMax(max);

View File

@@ -216,7 +216,16 @@ public class KeyFormattingUtils {
* @return * @return
*/ */
public static String convertFingerprintToHex(byte[] fingerprint) { 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) { public static long getKeyIdFromFingerprint(byte[] fingerprint) {

View File

@@ -136,7 +136,7 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView
where += " AND " + KeyRings.USER_ID + " LIKE ?"; where += " AND " + KeyRings.USER_ID + " LIKE ?";
return new CursorLoader(getContext(), baseUri, KeyAdapter.PROJECTION, where, return new CursorLoader(getContext(), baseUri, KeyAdapter.PROJECTION, where,
new String[] { "%" + query + "%" }, null); new String[]{"%" + query + "%"}, null);
} }
mAdapter.setSearchQuery(null); mAdapter.setSearchQuery(null);
@@ -156,7 +156,7 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView
@Override @Override
public void showDropDown() { public void showDropDown() {
if (mAdapter.getCursor().isClosed()) { if (mAdapter == null || mAdapter.getCursor() == null || mAdapter.getCursor().isClosed()) {
return; return;
} }
super.showDropDown(); super.showDropDown();

View File

@@ -26,7 +26,7 @@ import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader; import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter; import android.support.v4.widget.CursorAdapter;
import android.support.v7.widget.AppCompatSpinner; import android.support.v7.widget.AppCompatSpinner;
import android.text.format.DateFormat; import android.text.format.DateUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -42,10 +42,6 @@ import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.util.Log; 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. * Use AppCompatSpinner from AppCompat lib instead of Spinner. Fixes white dropdown icon.
* Related: http://stackoverflow.com/a/27713090 * Related: http://stackoverflow.com/a/27713090
@@ -164,14 +160,14 @@ public abstract class KeySpinner extends AppCompatSpinner implements LoaderManag
boolean duplicate = cursor.getLong(mIndexDuplicate) > 0; boolean duplicate = cursor.getLong(mIndexDuplicate) > 0;
if (duplicate) { if (duplicate) {
Date creationDate = new Date(cursor.getLong(mIndexCreationDate) * 1000); String dateTime = DateUtils.formatDateTime(context,
Calendar creationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); cursor.getLong(mIndexCreationDate) * 1000,
creationCal.setTime(creationDate); DateUtils.FORMAT_SHOW_DATE
// convert from UTC to time zone of device | DateUtils.FORMAT_SHOW_TIME
creationCal.setTimeZone(TimeZone.getDefault()); | DateUtils.FORMAT_SHOW_YEAR
| DateUtils.FORMAT_ABBREV_MONTH);
vDuplicate.setText(context.getString(R.string.label_creation) + ": " vDuplicate.setText(context.getString(R.string.label_key_created, dateTime));
+ DateFormat.getDateFormat(context).format(creationCal.getTime()));
vDuplicate.setVisibility(View.VISIBLE); vDuplicate.setVisibility(View.VISIBLE);
} else { } else {
vDuplicate.setVisibility(View.GONE); vDuplicate.setVisibility(View.GONE);

View File

@@ -446,6 +446,13 @@ public class ContactHelper {
writeKeysToMainProfileContact(context, resolver); 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<Long> deletedKeys = getRawContactMasterKeyIds(resolver); Set<Long> deletedKeys = getRawContactMasterKeyIds(resolver);
// Load all public Keys from OK // Load all public Keys from OK
@@ -519,6 +526,9 @@ public class ContactHelper {
* @param context * @param context
*/ */
public static void writeKeysToMainProfileContact(Context context, ContentResolver resolver) { public static void writeKeysToMainProfileContact(Context context, ContentResolver resolver) {
// deletes contacts hidden by the user so they can be reinserted if necessary
deleteFlaggedMainProfileRawContacts(resolver);
Set<Long> keysToDelete = getMainProfileMasterKeyIds(resolver); Set<Long> keysToDelete = getMainProfileMasterKeyIds(resolver);
// get all keys which have associated secret keys // get all keys which have associated secret keys
@@ -585,7 +595,7 @@ public class ContactHelper {
* *
* @param resolver * @param resolver
* @param masterKeyId * @param masterKeyId
* @return * @return number of rows deleted
*/ */
private static int deleteMainProfileRawContactByMasterKeyId(ContentResolver resolver, private static int deleteMainProfileRawContactByMasterKeyId(ContentResolver resolver,
long masterKeyId) { 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 * Delete all raw contacts associated to OpenKeychain, including those from "me" contact
* defined by ContactsContract.Profile * 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 * @return a set of all key master key ids currently present in the contact db
*/ */

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
android:id="@+id/toolbar_include"
layout="@layout/toolbar_standalone" />
<LinearLayout
android:layout_below="@id/toolbar_include"
android:padding="16dp"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<TextView
android:id="@+id/api_select_keys_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/api_select_keys_text" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/api_allowed_keys_list_fragment"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</ScrollView>
</LinearLayout>
</RelativeLayout>

View File

@@ -22,8 +22,7 @@
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:layout_marginLeft="8dp" android:layout_marginLeft="8dp"
android:textAppearance="?android:attr/textAppearanceMedium" android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Hold Yubikey against device dawg" android:text="@string/yubikey_create" />
/>
<ImageView <ImageView
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@@ -1,99 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:id="@+id/status_divider"
android:layout_height="1dip"
android:layout_width="match_parent"
android:background="?android:attr/listDivider" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="4dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:orientation="horizontal"
android:id="@+id/decrypt_file_browse"
android:clickable="true"
android:background="?android:selectableItemBackground">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingLeft="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/label_file_colon"
android:gravity="center_vertical" />
<TextView
android:id="@+id/decrypt_file_filename"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="@string/filemanager_title_open"
android:drawableRight="@drawable/ic_folder_grey_24dp"
android:drawablePadding="8dp"
android:gravity="center_vertical" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:background="?android:attr/listDivider"
android:layout_marginBottom="8dp" />
<CheckBox
android:id="@+id/decrypt_file_delete_after_decryption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_delete_after_decryption" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/decrypt_file_action_decrypt"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:text="@string/btn_decrypt_verify_file"
android:clickable="true"
android:background="?android:selectableItemBackground"
android:drawableRight="@drawable/ic_save_grey_24dp"
android:drawablePadding="8dp"
android:gravity="center_vertical"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:background="?android:attr/listDivider"
android:layout_above="@+id/decrypt_file_action_decrypt" />
</RelativeLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>

View File

@@ -5,7 +5,7 @@
<include <include
android:id="@+id/toolbar_include" android:id="@+id/toolbar_include"
layout="@layout/toolbar_standalone_white" /> layout="@layout/toolbar_result_decrypt" />
<!-- <!--
fitsSystemWindows and layout_marginTop from fitsSystemWindows and layout_marginTop from

View File

@@ -0,0 +1,149 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:fillViewport="true"
android:paddingTop="8dp"
android:layout_width="match_parent"
android:scrollbars="vertical"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:id="@+id/status_divider"
android:layout_height="1dip"
android:layout_width="match_parent"
android:background="?android:attr/listDivider" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="4dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:orientation="horizontal"
android:id="@+id/decrypt_files_browse"
android:clickable="true"
android:background="?android:selectableItemBackground">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingLeft="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/label_file_colon"
android:gravity="center_vertical" />
<TextView
android:id="@+id/decrypt_files_filename"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="@string/filemanager_title_open"
android:drawableRight="@drawable/ic_folder_grey_24dp"
android:drawablePadding="8dp"
android:gravity="center_vertical" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:background="?android:attr/listDivider"
android:layout_marginBottom="8dp" />
<CheckBox
android:id="@+id/decrypt_files_delete_after_decryption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_delete_after_decryption" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/decrypt_files_action_decrypt"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:text="@string/btn_decrypt_verify_file"
android:clickable="true"
android:background="?android:selectableItemBackground"
android:drawableRight="@drawable/ic_save_grey_24dp"
android:drawablePadding="8dp"
android:gravity="center_vertical"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:background="?android:attr/listDivider"
android:layout_above="@+id/decrypt_files_action_decrypt" />
</RelativeLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>
</LinearLayout>
<!-- TODO: Use this layout later to hide file list -->
<LinearLayout
android:visibility="gone"
android:id="@+id/decrypt_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"></LinearLayout>
<LinearLayout
android:visibility="gone"
android:id="@+id/decrypt_error_overlay"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/decrypt_invalid_text"
android:padding="16dp"
android:layout_gravity="center"
android:textColor="@color/android_red_light" />
<Button
android:id="@+id/decrypt_error_overlay_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/button_edgy"
android:textColor="@color/android_red_light"
android:text="@string/decrypt_invalid_button"
android:layout_gravity="center_horizontal" />
</LinearLayout>
</LinearLayout>

View File

@@ -7,7 +7,7 @@
<LinearLayout <LinearLayout
android:visibility="gone" android:visibility="gone"
android:id="@+id/decrypt_text_valid" android:id="@+id/decrypt_content"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
@@ -37,7 +37,7 @@
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/decrypt_text_invalid" android:id="@+id/decrypt_error_overlay"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
@@ -54,7 +54,7 @@
android:textColor="@color/android_red_light" /> android:textColor="@color/android_red_light" />
<Button <Button
android:id="@+id/decrypt_text_invalid_button" android:id="@+id/decrypt_error_overlay_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/button_edgy" android:background="@drawable/button_edgy"
@@ -62,4 +62,4 @@
android:text="@string/decrypt_invalid_button" android:text="@string/decrypt_invalid_button"
android:layout_gravity="center_horizontal" /> android:layout_gravity="center_horizontal" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@@ -1,19 +1,26 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<android.support.v7.widget.Toolbar <android.support.v7.widget.Toolbar
android:id="@+id/activity_main_toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="wrap_content"
android:elevation="4dp" android:elevation="4dp"
android:background="?attr/colorPrimary"/> android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
tools:ignore="UnusedAttribute" />
<FrameLayout <FrameLayout
android:id="@+id/main_fragment_container" android:id="@+id/main_fragment_container"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_width="match_parent"/> android:layout_width="match_parent" />
</LinearLayout> </LinearLayout>

View File

@@ -1,6 +1,6 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
## 3.2beta2 ## 3.2
* Material design * Material design
* Integration of QR Scanner (New permissions required) * Integration of QR Scanner (New permissions required)

View File

@@ -1,6 +1,6 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
## 3.2beta2 ## 3.2
* Material design * Material design
* Integration of QR Scanner (New permissions required) * Integration of QR Scanner (New permissions required)

View File

@@ -1,6 +1,6 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
## 3.2beta2 ## 3.2
* Material-Design * Material-Design
* QR-Scanner-Integration (benötigt neue Berechtigungen) * QR-Scanner-Integration (benötigt neue Berechtigungen)

View File

@@ -1,8 +1,8 @@
[//]: # (NOTA: ¡Por favor ponga cada frase en su propia línea, Transifex pone cada línea en su propio campo de traducción!) [//]: # (NOTA: ¡Por favor ponga cada frase en su propia línea, Transifex pone cada línea en su propio campo de traducción!)
## 3.2beta2 ## 3.2
* Diseño estilo Material * Material design (estilo)
* Integración de QR Scanner (se requieren nuevos permisos) * Integración de QR Scanner (se requieren nuevos permisos)
* Asistente de creación de clave mejorado * Asistente de creación de clave mejorado
* Repara contactos perdidos después de la sincronización * Repara contactos perdidos después de la sincronización

View File

@@ -1,6 +1,6 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
## 3.2beta2 ## 3.2
* Material design * Material design
* Integration of QR Scanner (New permissions required) * Integration of QR Scanner (New permissions required)

View File

@@ -1,6 +1,6 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
## 3.2beta2 ## 3.2
* Material design * Material design
* Integration of QR Scanner (New permissions required) * Integration of QR Scanner (New permissions required)

View File

@@ -1,6 +1,6 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
## 3.2beta2 ## 3.2
* Material design * Material design
* Integration of QR Scanner (New permissions required) * Integration of QR Scanner (New permissions required)

View File

@@ -1,6 +1,6 @@
[//] : # (NOTE : veuillez mettre chaque phrase sur sa propre ligne. Transifex met chaque ligne dans son propre champ de traduction !) [//] : # (NOTE : veuillez mettre chaque phrase sur sa propre ligne. Transifex met chaque ligne dans son propre champ de traduction !)
## 3.2beta2 ## 3.2
* Conception matérielle * Conception matérielle
* Intégration du lecteur QR (nouvelles permissions exigées) * Intégration du lecteur QR (nouvelles permissions exigées)

View File

@@ -1,6 +1,6 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
## 3.2beta2 ## 3.2
* Material design * Material design
* Integration of QR Scanner (New permissions required) * Integration of QR Scanner (New permissions required)

View File

@@ -1,6 +1,6 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
## 3.2beta2 ## 3.2
* Material design * Material design
* Integration of QR Scanner (New permissions required) * Integration of QR Scanner (New permissions required)

View File

@@ -1,28 +1,28 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
## Key Confirmation ## 鍵の検証
Without confirmation, you cannot be sure if a key really corresponds to a specific person. キーが本当に特定の人物に対応するか検証しないで、あなたは使うことができません。
The simplest way to confirm a key is by scanning the QR Code or exchanging it via NFC. 鍵の検証の最も単純な方法はQRコードのスキャンもしくはNFCでの交換をすることです。
To confirm keys between more than two persons, we suggest to use the key exchange method available for your keys. 2人以上の間で鍵の検証するなら、あなたの鍵にある鍵交換メソッドで使えるものを提案します。
## Key Status ## 鍵ステータス
<img src="status_signature_verified_cutout_24dp"/> <img src="status_signature_verified_cutout_24dp"/>
Confirmed: You have already confirmed this key, e.g., by scanning the QR Code. 検証済み:あなたは既に鍵を検証しています、e.g.QRコードのスキャン。
<img src="status_signature_unverified_cutout_24dp"/> <img src="status_signature_unverified_cutout_24dp"/>
Unconfirmed: This key has not been confirmed yet. You cannot be sure if the key really corresponds to a specific person. 未検証: この鍵はまだ検証されていません。あなたはこの鍵が特定の個人と結び付くとして利用することができません。
<img src="status_signature_expired_cutout_24dp"/> <img src="status_signature_expired_cutout_24dp"/>
Expired: This key is no longer valid. Only the owner can extend its validity. 期限切れ: この鍵はすでに有効ではありません。鍵の主だけが鍵の有効期間を拡大することができます。
<img src="status_signature_revoked_cutout_24dp"/> <img src="status_signature_revoked_cutout_24dp"/>
Revoked: This key is no longer valid. It has been revoked by its owner. 破棄済み: この鍵は有効ではありません。鍵の主がすでに破棄しています。
## Advanced Information ## 詳細情報
A "key confirmation" in OpenKeychain is implemented by creating a certification according to the OpenPGP standard. OpenKeychainでの"鍵の検証"はOpenPGP標準に準拠した証明を生成する実装がなされています。
This certification is a ["generic certification (0x10)"](http://tools.ietf.org/html/rfc4880#section-5.2.1) described in the standard by: この証明は ["汎用証明 (0x10)"](http://tools.ietf.org/html/rfc4880#section-5.2.1) として標準に以下として記述されています:
"The issuer of this certification does not make any particular assertion as to how well the certifier has checked that the owner of the key is in fact the person described by the User ID." "The issuer of this certification does not make any particular assertion as to how well the certifier has checked that the owner of the key is in fact the person described by the User ID."
Traditionally, certifications (also with higher certification levels, such as "positive certifications" (0x13)) are organized in OpenPGP's Web of Trust. 歴史的に、証明(またより高いレベルの証明、"肯定的な証明" (0x13)) は OpenPGPによるWeb of Trustとして組織化されます。
Our model of key confirmation is a much simpler concept to avoid common usability problems related to this Web of Trust. われわれの鍵の証明モデルはとてもシンプルなコンセプトによって関連する一般的なユーザビリティの問題を回避する概念です。
We assume that keys are verified only to a certain degree that is still usable enough to be executed "on the go". We assume that keys are verified only to a certain degree that is still usable enough to be executed "on the go".
We also do not implement (potentially transitive) trust signatures or an ownertrust database like in GnuPG. We also do not implement (potentially transitive) trust signatures or an ownertrust database like in GnuPG.
Furthermore, keys which contain at least one user ID certified by a trusted key will be marked as "confirmed" in the key listings. Furthermore, keys which contain at least one user ID certified by a trusted key will be marked as "confirmed" in the key listings.

View File

@@ -1,6 +1,6 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
## 3.2beta2 ## 3.2
* マテリアルデザイン * マテリアルデザイン
* QRスキャナの統合 (新しいパーミッションを必要とします) * QRスキャナの統合 (新しいパーミッションを必要とします)
@@ -47,13 +47,13 @@
## 3.0 ## 3.0
* Yubikeyでの署名生成と復号化のフルサポート * Yubikeyでの署名生成と復号化のフルサポート
* Propose installable compatible apps in apps list * インストールできるAPIの互換性のあるアプリをアプリ内リストで提示します
* New design for decryption screens * 復号化画面を新しいデザインに
* Many fixes for key import, also fixes stripped keys * 鍵のインポートで沢山の修正、また鍵のストリップでも修正
* Honor and display key authenticate flags * 鍵の認証フラグの表示と設定
* User interface to generate custom keys * カスタムした鍵の生成のユーザーインタフェース
* Fixing user id revocation certificates * ユーザーID破棄証明の修正
* New cloud search (searches over traditional keyservers and keybase.io) * 新しいクラウド検索 (古典的な keyserver keybase.io から検索します)
* OpenKeychain内で鍵をストリップするのをサポートしました * OpenKeychain内で鍵をストリップするのをサポートしました
@@ -115,155 +115,155 @@ Vincent Breitmoser (GSoC 2014), mar-v-in (GSoC 2014), Daniel Albert, Art O Catha
## 2.6 ## 2.6
* 鍵証明 (ありがとうVincent Breitmoser) * 鍵証明 (ありがとうVincent Breitmoser)
* Support for GnuPG partial secret keys (thanks to Vincent Breitmoser) * GnuPGの部分秘密鍵のサポート (ありがとうVincent Breitmoser)
* New design for signature verification * 新しいデザインでの署名の検証
* Custom key length (thanks to Greg Witczak) * カスタムの鍵長 (ありがとうGreg Witczak)
* Fix share-functionality from other apps * 他のアプリからの共有ファンクションの修正
## 2.5 ## 2.5
* Fix decryption of symmetric OpenPGP messages/files * 対称暗号化PGPメッセージ/ファイルの復号化を修正
* Refactored key edit screen (thanks to Ash Hughes) * 鍵編集画面のリファクタ (ありがとうAsh Hughes)
* New modern design for encrypt/decrypt screens * 暗号化/復号化画面を新しいモダンなデザインに
* OpenPGP API version 3 (multiple api accounts, internal fixes, key lookup) * OpenPGP API バージョン 3 (複数APIアカウント, 内部修正,鍵検索)
## 2.4 ## 2.4
Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free! このリリースにおいて適用したリッチでバグのない機能を作ってくれたGoogle Summer of Code 2014の参加者たちに感謝を!
Besides several small patches, a notable number of patches are made by the following people (in alphabetical order): また、以下の人達(アルファベット順)の作ってくれたいくつもの小さなパッチや相当数のパッチにも:
Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser. Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.
* New unified key list * 新しい統合キーリスト
* Colorized key fingerprint * 鍵指紋のカラー化
* Support for keyserver ports * 鍵サーバのポート設定のサポート
* Deactivate possibility to generate weak keys * 弱い鍵の生成が可能だったのを無効化
* Much more internal work on the API * さらなるAPIでの内部動作
* Certify user ids * ユーザーIDの証明
* Keyserver query based on machine-readable output * 鍵サーバへの要求をマシンリーダブル出力を基盤にした
* Lock navigation drawer on tablets * タブレットでのナビゲーションドロワーのロック
* Suggestions for emails on creation of keys * 鍵の生成でのメールについての提案
* Search in public key lists * 公開鍵リスト内での検索
* And much more improvements and fixes… * そしてさらなる改善と修正...
## 2.3.1 ## 2.3.1
* Hotfix for crash when upgrading from old versions * 古いバージョンからのアップデートでクラッシュすることに対するホットフィックス
## 2.3 ## 2.3
* Remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes) * 秘密鍵のエクスポート時における必要でない公開鍵のエクスポートの削除 (ありがとうAsh Hughes)
* Fix setting expiry dates on keys (thanks to Ash Hughes) * 鍵の期限日時設定の修正 (ありがとうAsh Hughes)
* More internal fixes when editing keys (thanks to Ash Hughes) * 鍵編集時のさらなる内部修正 (ありがとうAsh Hughes)
* Querying keyservers directly from the import screen * インポート画面から直接鍵サーバへ要求するようにした
* Fix layout and dialog style on Android 2.2-3.0 * Android 2.2から3.0でのレイアウトとダイアログスタイルの修正
* Fix crash on keys with empty user ids * 空ユーザIDの鍵でのクラッシュ修正
* Fix crash and empty lists when coming back from signing screen * 署名画面から戻ってきたときにリストが空だとクラッシュするのを修正
* Bouncy Castle (cryptography library) updated from 1.47 to 1.50 and build from source * Bouncy Castle (cryptography library) 1.47 から 1.50アップデートおよびソースからのビルド
* Fix upload of key from signing screen * 署名画面からの鍵のアップロード修正
## 2.2 ## 2.2
* New design with navigation drawer * ナビゲーションドロワー付きの新しいデザイン
* New public key list design * 新しい公開鍵リストデザイン
* New public key view * 新しい公開鍵ビュー
* Bug fixes for importing of keys * 鍵のインポート時のバグ修正
* Key cross-certification (thanks to Ash Hughes) * 鍵のクロス証明 (ありがとうAsh Hughes)
* Handle UTF-8 passwords properly (thanks to Ash Hughes) * 適切な UTF-8 パスワード処理 (ありがとうAsh Hughes)
* First version with new languages (thanks to the contributors on Transifex) * 新しい言語での最初のバージョン (ありがとうTransifexの貢献者達)
* Sharing of keys via QR Codes fixed and improved * QRコードによる鍵共有の修正と改善
* Package signature verification for API * APIでのパッケージ署名検証
## 2.1.1 ## 2.1.1
* API Updates, preparation for K-9 Mail integration * APIアップデート、K-9 Mail統合準備
## 2.1 ## 2.1
* Lots of bug fixes * たくさんのバグ修正
* New API for developers * 開発者向け新API
* PRNG bug fix by Google * Googleによる擬似乱数生成器バグの修正
## 2.0 ## 2.0
* Complete redesign * 再デザイン完了
* Share public keys via QR codes, NFC beam * QRコード、NFCビームでの公開鍵共有
* Sign keys * 鍵への署名
* Upload keys to server * 鍵サーバへアップロード
* Fixes import issues * インポート問題修正
* New AIDL API * 新しいAIDL API
## 1.0.8 ## 1.0.8
* Basic keyserver support * 鍵サーバの基本サポート
* App2sd * App2SD
* More choices for passphrase cache: 1, 2, 4, 8, hours * パスフレーズのキャッシュ時間についての選択肢追加: 1,2,4,8時間
* Translations: Norwegian (thanks, Sander Danielsen), Chinese (thanks, Zhang Fredrick) * 翻訳: ノルウェー語 (ありがとう、Sander Danielsen)、中国語 (ありがとう、Zhang Fredrick) 追加
* Bugfixes * バグ修正
* Optimizations * 最適化
## 1.0.7 ## 1.0.7
* Fixed problem with signature verification of texts with trailing newline * 改行を含まないテキストの署名検証問題の修正
* More options for passphrase cache time to live (20, 40, 60 mins) * パスフレーズのキャッシュ生存時間 (20,40,60分) のオプション追加
## 1.0.6 ## 1.0.6
* Account adding crash on Froyo fixed * Froyo でのアカウント追加時クラッシュの修正
* Secure file deletion * セキュアファイル削除
* Option to delete key file after import * 鍵ファイルインポート後の削除オプション
* Stream encryption/decryption (gallery, etc.) * ストリーム暗号化/復号化 (ギャラリーなど)
* New options (language, force v3 signatures) * 新しいオプション (言語、強制V3署名)
* Interface changes * インタフェース変更
* Bugfixes * バグ修正
## 1.0.5 ## 1.0.5
* German and Italian translation * ドイツ語およびイタリア語翻訳追加
* Much smaller package, due to reduced BC sources * BCソースが重複してしまっていたことによる、より小さいパッケージ化
* New preferences GUI * 新しい設定GUI
* Layout adjustment for localization * ローカライズでのレイアウトを適合化
* Signature bugfix * 署名バグ修正
## 1.0.4 ## 1.0.4
* Fixed another crash caused by some SDK bug with query builder * クエリービルダーによるSDKのいくつかのバグによるクラッシュの修正
## 1.0.3 ## 1.0.3
* Fixed crashes during encryption/signing and possibly key export * 鍵エクスポートできる時と暗号化/署名中のクラッシュ修正
## 1.0.2 ## 1.0.2
* Filterable key lists * 鍵リストのフィルタ可能化
* Smarter pre-selection of encryption keys * 暗号化鍵の事前選択のよりスマートな実装
* New Intent handling for VIEW and SEND, allows files to be encrypted/decrypted out of file managers * VIEWおよびSENDについて新しいインテントのハンドリング、ファイルマネージャ外のファイルを暗号化/復号化するのを受け付けるようになる。
* Fixes and additional features (key preselection) for K-9 Mail, new beta build available * K-9 Mailにおける修正と追加機能 (鍵事前選択)、新しいベータビルド提供
## 1.0.1 ## 1.0.1
* GMail account listing was broken in 1.0.0, fixed again * 1.0.0で壊れたGmailアカウントリストアップを再度修正
## 1.0.0 ## 1.0.0
* K-9 Mail integration, APG supporting beta build of K-9 Mail * K-9 Mail 統合、K-9 MailでのAPG サポートのベータビルド
* Support of more file managers (including ASTRO) * (ASTROを含む)ファイルマネージャのさらなるサポート
* Slovenian translation * スロベニア語翻訳追加
* New database, much faster, less memory usage * より早くてメモリ使用量の少ない新しいデータベース
* Defined Intents and content provider for other apps * 他のアプリでのインテントおよびコンテンツプロバイダの定義
* Bugfixes * バグ修正

View File

@@ -1,16 +1,16 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
## How do I activate OpenKeychain in K-9 Mail? ## OpenKeychain K-9 Mailで有効にするにはどうすればよいか?
To use OpenKeychain with K-9 Mail, you want to follow these steps: OpenKeychain K-9 Mail と使うには, 次のステップを進めてください:
1. Open K-9 Mail and long-tap on the account you want to use OpenKeychain with. 1. K-9 Mail を開き、OpenKeychain を使いたいアカウントでロングタップしてください。
2. Select "Account settings", scroll to the very bottom and click "Cryptography". 2. "アカウントの設定" を選択、下のほうにある"暗号化"までスクロールし、クリックしてください。
3. Click on "OpenPGP Provider" and select OpenKeychain from the list. 3. "OpenPGP プロバイダ" をクリックし、リストからOpenKeychainを選択してください。
## I found a bug in OpenKeychain! ## OpenKeychainでバグを発見したなら!
Please report the bug using the [issue tracker of OpenKeychain](https://github.com/openpgp-keychain/openpgp-keychain/issues). [OpenKeychainの課題トラッカ](https://github.com/openpgp-keychain/openpgp-keychain/issues)を使ってバグを報告してください。
## Contribute ## 提供
If you want to help us developing OpenKeychain by contributing code [follow our small guide on Github](https://github.com/openpgp-keychain/openpgp-keychain#contribute-code). OpenKeychain へコードを提供し、開発を助けてくださるのなら [Githubの小ガイドを参照](https://github.com/openpgp-keychain/openpgp-keychain#contribute-code)してください。
## Translations ## 翻訳
Help translating OpenKeychain! Everybody can participate at [OpenKeychain on Transifex](https://www.transifex.com/projects/p/open-keychain/). OpenKeychain の翻訳を助けてください! [TransifexのOpenKeychain](https://www.transifex.com/projects/p/open-keychain/)にだれでも参加することができます。

View File

@@ -1,6 +1,6 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
## 3.2beta2 ## 3.2
* Material design * Material design
* Integratie van QR-scanner (nieuwe machtigingen vereist) * Integratie van QR-scanner (nieuwe machtigingen vereist)

View File

@@ -1,6 +1,6 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
## 3.2beta2 ## 3.2
* Material design * Material design
* Integration of QR Scanner (New permissions required) * Integration of QR Scanner (New permissions required)

View File

@@ -1,6 +1,6 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
## 3.2beta2 ## 3.2
* Material design * Material design
* Integration of QR Scanner (New permissions required) * Integration of QR Scanner (New permissions required)

View File

@@ -1,6 +1,6 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
## 3.2beta2 ## 3.2
* Material design * Material design
* Integration of QR Scanner (New permissions required) * Integration of QR Scanner (New permissions required)

View File

@@ -2,12 +2,12 @@
[http://www.openkeychain.org](http://www.openkeychain.org) [http://www.openkeychain.org](http://www.openkeychain.org)
[OpenKeychain](http://www.openkeychain.org) is an OpenPGP implementation for Android. [OpenKeychain](http://www.openkeychain.org) это OpenPGP имплементация для ОС Android.
License: GPLv3+ Лицензия: GPLv3+
## Developers ## Разработчики
* Dominik Schürmann (Maintainer) * Dominik Schürmann (Ведущий разработчик)
* Art O Cathain * Art O Cathain
* Ash Hughes * Ash Hughes
* Brian C. Barnes * Brian C. Barnes
@@ -28,7 +28,7 @@ License: GPLv3+
* Tim Bray * Tim Bray
* Vincent Breitmoser * Vincent Breitmoser
## Libraries ## Используемые библиотеки
* [SpongyCastle](http://rtyley.github.com/spongycastle/) (MIT X11 License) * [SpongyCastle](http://rtyley.github.com/spongycastle/) (MIT X11 License)
* [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT License) * [SafeSlinger Exchange library](https://github.com/SafeSlingerProject/exchange-android) (MIT License)
* [Android Support Libraries](http://developer.android.com/tools/support-library/index.html) (Apache License v2) * [Android Support Libraries](http://developer.android.com/tools/support-library/index.html) (Apache License v2)

View File

@@ -1,22 +1,22 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
## Key Confirmation ## Подтверждение ключей
Without confirmation, you cannot be sure if a key really corresponds to a specific person. Без подтверждения Вы не можете быть уверены, что ключ принадлежит определенному человеку.
The simplest way to confirm a key is by scanning the QR Code or exchanging it via NFC. Простейший способ подтвердить - отсканировать QR код или получить ключ через NFC.
To confirm keys between more than two persons, we suggest to use the key exchange method available for your keys. To confirm keys between more than two persons, we suggest to use the key exchange method available for your keys.
## Key Status ## Статус ключей
<img src="status_signature_verified_cutout_24dp"/> <img src="status_signature_verified_cutout_24dp"/>
Confirmed: You have already confirmed this key, e.g., by scanning the QR Code. Подтверждён: Вы уже подтвердили этот ключ, напр. отсканировав QR код.
<img src="status_signature_unverified_cutout_24dp"/> <img src="status_signature_unverified_cutout_24dp"/>
Unconfirmed: This key has not been confirmed yet. You cannot be sure if the key really corresponds to a specific person. Не подтверждён: Этот ключ ещё не прошел проверку. Вы не можете быть уверены, что ключ принадлежит определенному человеку.
<img src="status_signature_expired_cutout_24dp"/> <img src="status_signature_expired_cutout_24dp"/>
Expired: This key is no longer valid. Only the owner can extend its validity. Просрочен: Срок годности ключа истёк. Только его владелец может продлить срок годности.
<img src="status_signature_revoked_cutout_24dp"/> <img src="status_signature_revoked_cutout_24dp"/>
Revoked: This key is no longer valid. It has been revoked by its owner. Отозван: Этот ключ больше не действителен. Владелец ключа отозвал его.
## Advanced Information ## Подробная информация
A "key confirmation" in OpenKeychain is implemented by creating a certification according to the OpenPGP standard. A "key confirmation" in OpenKeychain is implemented by creating a certification according to the OpenPGP standard.
This certification is a ["generic certification (0x10)"](http://tools.ietf.org/html/rfc4880#section-5.2.1) described in the standard by: This certification is a ["generic certification (0x10)"](http://tools.ietf.org/html/rfc4880#section-5.2.1) described in the standard by:
"The issuer of this certification does not make any particular assertion as to how well the certifier has checked that the owner of the key is in fact the person described by the User ID." "The issuer of this certification does not make any particular assertion as to how well the certifier has checked that the owner of the key is in fact the person described by the User ID."

View File

@@ -1,6 +1,6 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
## 3.2beta2 ## 3.2
* Material design * Material design
* Integration of QR Scanner (New permissions required) * Integration of QR Scanner (New permissions required)

View File

@@ -1,6 +1,6 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
## 3.2beta2 ## 3.2
* Material design * Material design
* Integration of QR Scanner (New permissions required) * Integration of QR Scanner (New permissions required)

View File

@@ -1,6 +1,6 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
## 3.2beta2 ## 3.2
* Material design * Material design
* Integration of QR Scanner (New permissions required) * Integration of QR Scanner (New permissions required)

View File

@@ -1,6 +1,6 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
## 3.2beta2 ## 3.2
* Material design * Material design
* Integration of QR Scanner (New permissions required) * Integration of QR Scanner (New permissions required)

View File

@@ -1,6 +1,6 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
## 3.2beta2 ## 3.2
* Material design * Material design
* Integration of QR Scanner (New permissions required) * Integration of QR Scanner (New permissions required)

View File

@@ -2,9 +2,9 @@
[http://www.openkeychain.org](http://www.openkeychain.org) [http://www.openkeychain.org](http://www.openkeychain.org)
[OpenKeychain](http://www.openkeychain.org) is an OpenPGP implementation for Android. <a href="http://www.openkeychain.org">OpenKeychain</a>是一個Android的OpenPGP應用。
License: GPLv3+ 授權: GPLv3+
## Developers ## Developers
* Dominik Schürmann (Maintainer) * Dominik Schürmann (Maintainer)

View File

@@ -1,23 +1,23 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
## Key Confirmation 金鑰認證
Without confirmation, you cannot be sure if a key really corresponds to a specific person. 這個身分識別尚未經過認證,你不能確認這個身分識別是否屬於真的某個人。
The simplest way to confirm a key is by scanning the QR Code or exchanging it via NFC. 最簡單確認金鑰的方式就是透過掃描QR code或是經由NFC交換。
To confirm keys between more than two persons, we suggest to use the key exchange method available for your keys. To confirm keys between more than two persons, we suggest to use the key exchange method available for your keys.
## Key Status ## 金鑰狀態
<img src="status_signature_verified_cutout_24dp"/> <img src="status_signature_verified_cutout_24dp"/>
Confirmed: You have already confirmed this key, e.g., by scanning the QR Code. 已認證: 你已經認證了這個金鑰,例如透過掃描QR Code
<img src="status_signature_unverified_cutout_24dp"/> <img src="status_signature_unverified_cutout_24dp"/>
Unconfirmed: This key has not been confirmed yet. You cannot be sure if the key really corresponds to a specific person. 未確認:這個身分識別尚未經過認證,你不能確認這個身分識別是否屬於真的某個人。
<img src="status_signature_expired_cutout_24dp"/> <img src="status_signature_expired_cutout_24dp"/>
Expired: This key is no longer valid. Only the owner can extend its validity. 已過期:這個金鑰因超過有效期限而失效。只有金鑰擁有者可以改變有效期限。
<img src="status_signature_revoked_cutout_24dp"/> <img src="status_signature_revoked_cutout_24dp"/>
Revoked: This key is no longer valid. It has been revoked by its owner. 已撤銷:這個金鑰已經被擁有者撤銷而失效。
## Advanced Information ## Advanced Information
A "key confirmation" in OpenKeychain is implemented by creating a certification according to the OpenPGP standard. 在OpenKeychain中透過根據標準OpenPGP所建立的證書可以簡單的認證一個金鑰。
This certification is a ["generic certification (0x10)"](http://tools.ietf.org/html/rfc4880#section-5.2.1) described in the standard by: This certification is a ["generic certification (0x10)"](http://tools.ietf.org/html/rfc4880#section-5.2.1) described in the standard by:
"The issuer of this certification does not make any particular assertion as to how well the certifier has checked that the owner of the key is in fact the person described by the User ID." "The issuer of this certification does not make any particular assertion as to how well the certifier has checked that the owner of the key is in fact the person described by the User ID."

View File

@@ -1,6 +1,6 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
## 3.2beta2 ## 3.2
* Material design * Material design
* Integration of QR Scanner (New permissions required) * Integration of QR Scanner (New permissions required)

View File

@@ -1,6 +1,6 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
## 3.2beta2 ## 3.2
* Material design * Material design
* Integration of QR Scanner (New permissions required) * Integration of QR Scanner (New permissions required)

View File

@@ -1,6 +1,6 @@
[//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!) [//]: # (NOTE: Please put every sentence in its own line, Transifex puts every line in its own translation field!)
## 3.2beta2 ## 3.2
* First version with full YubiKey support available from the user interface: Edit keys, bind YubiKey to keys,... * First version with full YubiKey support available from the user interface: Edit keys, bind YubiKey to keys,...
* Material design * Material design

View File

@@ -130,11 +130,13 @@
<plurals name="n_keys"> <plurals name="n_keys">
<item quantity="one">1 ключ</item> <item quantity="one">1 ключ</item>
<item quantity="few">%d ключей</item> <item quantity="few">%d ключей</item>
<item quantity="many">%d ключей</item>
<item quantity="other">%d ключей</item> <item quantity="other">%d ключей</item>
</plurals> </plurals>
<plurals name="n_keyservers"> <plurals name="n_keyservers">
<item quantity="one">%d сервер ключей</item> <item quantity="one">%d сервер ключей</item>
<item quantity="few">%d серверов ключей</item> <item quantity="few">%d серверов ключей</item>
<item quantity="many">%d серверов ключей</item>
<item quantity="other">%d серверов ключей</item> <item quantity="other">%d серверов ключей</item>
</plurals> </plurals>
<string name="secret_key">Секретный ключ:</string> <string name="secret_key">Секретный ключ:</string>
@@ -264,6 +266,7 @@
<plurals name="progress_exporting_key"> <plurals name="progress_exporting_key">
<item quantity="one">экспорт ключа...</item> <item quantity="one">экспорт ключа...</item>
<item quantity="few">экспорт ключей...</item> <item quantity="few">экспорт ключей...</item>
<item quantity="many">экспорт ключей...</item>
<item quantity="other">экспорт ключей...</item> <item quantity="other">экспорт ключей...</item>
</plurals> </plurals>
<string name="progress_extracting_signature_key">извлечение подписи ключа...</string> <string name="progress_extracting_signature_key">извлечение подписи ключа...</string>
@@ -336,6 +339,7 @@
<plurals name="import_keys_added_and_updated_1"> <plurals name="import_keys_added_and_updated_1">
<item quantity="one">Ключ успешно импортирован</item> <item quantity="one">Ключ успешно импортирован</item>
<item quantity="few">Успешно добавлено %1$d ключей</item> <item quantity="few">Успешно добавлено %1$d ключей</item>
<item quantity="many">Успешно добавлено %1$d ключей</item>
<item quantity="other">Успешно добавлено %1$d ключей</item> <item quantity="other">Успешно добавлено %1$d ключей</item>
</plurals> </plurals>
<string name="import_error_nothing">Нет данных для импорта.</string> <string name="import_error_nothing">Нет данных для импорта.</string>
@@ -388,6 +392,7 @@
<plurals name="key_list_selected_keys"> <plurals name="key_list_selected_keys">
<item quantity="one">1 ключ выбран.</item> <item quantity="one">1 ключ выбран.</item>
<item quantity="few">%d ключей выбрано.</item> <item quantity="few">%d ключей выбрано.</item>
<item quantity="many">%d ключей выбрано.</item>
<item quantity="other">%d ключей выбрано.</item> <item quantity="other">%d ключей выбрано.</item>
</plurals> </plurals>
<string name="key_list_empty_text1">Ключи не найдены!</string> <string name="key_list_empty_text1">Ключи не найдены!</string>
@@ -785,6 +790,7 @@
<plurals name="error_import_non_pgp_part"> <plurals name="error_import_non_pgp_part">
<item quantity="one">часть загруженного файла содержит данные OpenPGP, но это не ключ</item> <item quantity="one">часть загруженного файла содержит данные OpenPGP, но это не ключ</item>
<item quantity="few">части загруженного файла содержат данные OpenPGP, но это не ключ</item> <item quantity="few">части загруженного файла содержат данные OpenPGP, но это не ключ</item>
<item quantity="many">части загруженного файла содержат данные OpenPGP, но это не ключ</item>
<item quantity="other">части загруженного файла содержат данные OpenPGP, но это не ключ</item> <item quantity="other">части загруженного файла содержат данные OpenPGP, но это не ключ</item>
</plurals> </plurals>
<!--Messages for Export Log operation--> <!--Messages for Export Log operation-->

View File

@@ -436,6 +436,8 @@
<string name="key_view_action_edit">編輯金鑰</string> <string name="key_view_action_edit">編輯金鑰</string>
<string name="key_view_action_encrypt">加密文字</string> <string name="key_view_action_encrypt">加密文字</string>
<string name="key_view_action_encrypt_files">檔案</string> <string name="key_view_action_encrypt_files">檔案</string>
<string name="key_view_action_share_with">分享...</string>
<string name="key_view_action_share_nfc">以NFC分享</string>
<string name="key_view_action_upload">上傳到金鑰伺服器</string> <string name="key_view_action_upload">上傳到金鑰伺服器</string>
<string name="key_view_tab_main">摘要</string> <string name="key_view_tab_main">摘要</string>
<string name="key_view_tab_share">分享</string> <string name="key_view_tab_share">分享</string>
@@ -458,24 +460,37 @@
<item>撤銷身分識別</item> <item>撤銷身分識別</item>
</string-array> </string-array>
<string name="edit_key_edit_user_id_revoked">這個身分識別已被撤銷。此動作無法還原。</string> <string name="edit_key_edit_user_id_revoked">這個身分識別已被撤銷。此動作無法還原。</string>
<string name="edit_key_edit_subkey_title">選擇操作動作!</string>
<string-array name="edit_key_edit_subkey"> <string-array name="edit_key_edit_subkey">
<item>變更到期日</item> <item>變更到期日</item>
<item>撤銷子金鑰</item> <item>撤銷子金鑰</item>
<item>Strip Subkey</item> <item>Strip Subkey</item>
</string-array> </string-array>
<string name="edit_key_new_subkey">新增子金鑰</string>
<string name="edit_key_error_add_identity">新增至少一組身分識別!</string> <string name="edit_key_error_add_identity">新增至少一組身分識別!</string>
<string name="edit_key_error_add_subkey">新增至少一組子金鑰!</string> <string name="edit_key_error_add_subkey">新增至少一組子金鑰!</string>
<!--Create key--> <!--Create key-->
<string name="create_key_empty">必填欄位</string> <string name="create_key_empty">必填欄位</string>
<string name="create_key_passphrases_not_equal">口令不相符</string> <string name="create_key_passphrases_not_equal">口令不相符</string>
<string name="create_key_final_robot_text">建立金鑰可能需要一點時間,來杯咖啡吧…</string> <string name="create_key_final_robot_text">建立金鑰可能需要一點時間,來杯咖啡吧…</string>
<string name="create_key_name_text">選擇一個名字有關這個金鑰。 這可以是全名John Doe或是暱稱Johnny。</string>
<string name="create_key_passphrase_text">選擇一個安全性較佳的口令,當手機遺失或遭竊時可以保護你的金鑰。</string>
<string name="create_key_hint_full_name">全名或暱稱</string>
<string name="create_key_add_email">新增電子郵件</string>
<!--View key--> <!--View key-->
<string name="view_key_my_key">我的金鑰:</string>
<!--Navigation Drawer--> <!--Navigation Drawer-->
<string name="nav_keys">金鑰</string> <string name="nav_keys">金鑰</string>
<string name="nav_encrypt_decrypt">加密/解密</string>
<string name="nav_apps">應用程式</string> <string name="nav_apps">應用程式</string>
<string name="my_keys">我的金鑰</string> <string name="my_keys">我的金鑰</string>
<!--hints--> <!--hints-->
<!--certs--> <!--certs-->
<string name="cert_default">預設</string>
<string name="cert_none"></string>
<string name="cert_verify_ok">OK</string>
<string name="cert_verify_failed">失敗!</string>
<string name="cert_verify_error">錯誤!</string>
<!--LogType log messages. Errors should have _ERROR_ in their name and end with a !--> <!--LogType log messages. Errors should have _ERROR_ in their name and end with a !-->
<!--Import Public log entries--> <!--Import Public log entries-->
<!--Import Secret log entries--> <!--Import Secret log entries-->
@@ -540,6 +555,7 @@
<string name="error_no_encrypt_subkey">沒有可供加密的子金鑰!</string> <string name="error_no_encrypt_subkey">沒有可供加密的子金鑰!</string>
<string name="info_no_manual_account_creation">請不要自行建立OpenKeychain帳戶。\n更多資訊請參考說明。</string> <string name="info_no_manual_account_creation">請不要自行建立OpenKeychain帳戶。\n更多資訊請參考說明。</string>
<string name="exchange_description">要發起金鑰交換,先在右邊選擇與會人數,然後點選〝開始交換〞。\n\n接下來會詢問你兩個問題以確保會議成員與交換的指紋是正確的。</string> <string name="exchange_description">要發起金鑰交換,先在右邊選擇與會人數,然後點選〝開始交換〞。\n\n接下來會詢問你兩個問題以確保會議成員與交換的指紋是正確的。</string>
<string name="btn_start_exchange">開始交換</string>
<!--Passphrase wizard--> <!--Passphrase wizard-->
<!--TODO: rename all the things!--> <!--TODO: rename all the things!-->
<!--<string name="enter_passphrase_twice">Enter passphrase twice</string>--> <!--<string name="enter_passphrase_twice">Enter passphrase twice</string>-->

View File

@@ -42,7 +42,7 @@
<!-- section --> <!-- section -->
<string name="section_user_ids">"Identities"</string> <string name="section_user_ids">"Identities"</string>
<string name="section_yubikey">"Yubikey"</string> <string name="section_yubikey">"YubiKey"</string>
<string name="section_linked_system_contact">"Linked System Contact"</string> <string name="section_linked_system_contact">"Linked System Contact"</string>
<string name="section_should_you_trust">"Should you trust this key?"</string> <string name="section_should_you_trust">"Should you trust this key?"</string>
<string name="section_proof_details">Proof verification</string> <string name="section_proof_details">Proof verification</string>
@@ -138,7 +138,8 @@
<string name="label_file_compression">"File compression"</string> <string name="label_file_compression">"File compression"</string>
<string name="label_keyservers">"Keyservers"</string> <string name="label_keyservers">"Keyservers"</string>
<string name="label_key_id">"Key ID"</string> <string name="label_key_id">"Key ID"</string>
<string name="label_creation">"Key created %s"</string> <string name="label_key_created">"Key created %s"</string>
<string name="label_creation">"Creation"</string>
<string name="label_expiry">"Expiry"</string> <string name="label_expiry">"Expiry"</string>
<string name="label_usage">"Usage"</string> <string name="label_usage">"Usage"</string>
<string name="label_key_size">"Key Size"</string> <string name="label_key_size">"Key Size"</string>
@@ -505,7 +506,7 @@
<string name="api_settings_delete_account">"Delete account"</string> <string name="api_settings_delete_account">"Delete account"</string>
<string name="api_settings_package_name">"Package Name"</string> <string name="api_settings_package_name">"Package Name"</string>
<string name="api_settings_package_signature">"SHA-256 of Package Signature"</string> <string name="api_settings_package_signature">"SHA-256 of Package Signature"</string>
<string name="api_settings_accounts">"Accounts (deprecated API)"</string> <string name="api_settings_accounts">"Accounts (old API)"</string>
<string name="api_settings_advanced">"Extended Information"</string> <string name="api_settings_advanced">"Extended Information"</string>
<string name="api_settings_allowed_keys">"Allowed Keys"</string> <string name="api_settings_allowed_keys">"Allowed Keys"</string>
<string name="api_settings_settings">"Settings"</string> <string name="api_settings_settings">"Settings"</string>
@@ -523,6 +524,7 @@
<string name="api_select_pub_keys_text_no_user_ids">"Please select the recipients!"</string> <string name="api_select_pub_keys_text_no_user_ids">"Please select the recipients!"</string>
<string name="api_error_wrong_signature">"Signature check failed! Have you installed this app from a different source? If you are sure that this is not an attack, revoke this app's registration in OpenKeychain and then register the app again."</string> <string name="api_error_wrong_signature">"Signature check failed! Have you installed this app from a different source? If you are sure that this is not an attack, revoke this app's registration in OpenKeychain and then register the app again."</string>
<string name="api_select_sign_key_text">"Please select one of your existing keys or create a new one."</string> <string name="api_select_sign_key_text">"Please select one of your existing keys or create a new one."</string>
<string name="api_select_keys_text">"None of the allowed keys is able to decrypt the content. Please select the allowed keys."</string>
<!-- Share --> <!-- Share -->
<string name="share_qr_code_dialog_title">"Share with QR Code"</string> <string name="share_qr_code_dialog_title">"Share with QR Code"</string>
@@ -643,6 +645,7 @@
<string name="create_key_add_email">"Add email address"</string> <string name="create_key_add_email">"Add email address"</string>
<string name="create_key_add_email_text">"Additional email addresses are also associated to this key and can be used for secure communication."</string> <string name="create_key_add_email_text">"Additional email addresses are also associated to this key and can be used for secure communication."</string>
<string name="create_key_email_already_exists_text">"Email address has already been added"</string> <string name="create_key_email_already_exists_text">"Email address has already been added"</string>
<string name="create_key_email_invalid_email">"Email address format is invalid"</string>
<!-- View key --> <!-- View key -->
<string name="view_key_revoked">"Revoked: Key must not be used anymore!"</string> <string name="view_key_revoked">"Revoked: Key must not be used anymore!"</string>
@@ -1302,13 +1305,15 @@
<string name="yubikey_serno">"Serial No: %s"</string> <string name="yubikey_serno">"Serial No: %s"</string>
<string name="yubikey_key_holder">"Key holder: "</string> <string name="yubikey_key_holder">"Key holder: "</string>
<string name="yubikey_key_holder_unset">"Key holder: &lt;unset&gt;"</string> <string name="yubikey_key_holder_unset">"Key holder: &lt;unset&gt;"</string>
<string name="yubikey_status_bound">"Yubikey matches and is bound to key"</string> <string name="yubikey_status_bound">"YubiKey matches and is bound to key"</string>
<string name="yubikey_status_unbound">"Yubikey matches, can be bound to key"</string> <string name="yubikey_status_unbound">"YubiKey matches, can be bound to key"</string>
<string name="yubikey_status_partly">"Yubikey matches, partly bound to key"</string> <string name="yubikey_status_partly">"YubiKey matches, partly bound to key"</string>
<string name="yubikey_create">"Hold YubiKey against the back of your device."</string>
<string name="btn_import">"Import"</string> <string name="btn_import">"Import"</string>
<string name="snack_yubi_other">Different key stored on Yubikey!</string> <string name="snack_yubi_other">Different key stored on YubiKey!</string>
<string name="error_nfc">"NFC Error: %s"</string> <string name="error_nfc">"NFC Error: %s"</string>
<string name="error_pin_nodefault">Default PIN was rejected!</string> <string name="error_pin_nodefault">Default PIN was rejected!</string>
<string name="error_bluetooth_file">Error creating temporary file. Bluetooth sharing will fail.</string>
<string name="linked_create_https_1_1">"By creating a Linked Identity of this type, you can link your key to a website you control."</string> <string name="linked_create_https_1_1">"By creating a Linked Identity of this type, you can link your key to a website you control."</string>
<string name="linked_create_https_1_2">"To do this, you publish a text file on this website, then create a Linked Identity which links to it."</string> <string name="linked_create_https_1_2">"To do this, you publish a text file on this website, then create a Linked Identity which links to it."</string>

View File

@@ -8,17 +8,12 @@
<item name="colorPrimaryDark">@color/primary_dark</item> <item name="colorPrimaryDark">@color/primary_dark</item>
<item name="colorAccent">@color/accent</item> <item name="colorAccent">@color/accent</item>
<!-- remove actionbar and title, we use toolbar! -->
<item name="windowNoTitle">true</item> <item name="windowNoTitle">true</item>
<!-- remove actionbar, we use toolbar! -->
<item name="windowActionBar">false</item> <item name="windowActionBar">false</item>
<!-- multi selection should overlay Toolbar! http://stackoverflow.com/a/26450875 --> <!-- multi selection should overlay Toolbar! http://stackoverflow.com/a/26450875 -->
<item name="windowActionModeOverlay">true</item> <item name="windowActionModeOverlay">true</item>
<item name="searchViewStyle">@style/MySearchViewStyle</item> <item name="searchViewStyle">@style/MySearchViewStyle</item>
<!-- dark action bar... -->
<item name="theme">@style/ThemeOverlay.AppCompat.Dark</item>
<!-- ...but light popup menu (white background) -->
<item name="popupTheme">@style/ThemeOverlay.AppCompat.Light</item>
</style> </style>
<!-- http://android-developers.blogspot.de/2014/10/appcompat-v21-material-design-for-pre.html --> <!-- http://android-developers.blogspot.de/2014/10/appcompat-v21-material-design-for-pre.html -->

View File

@@ -9,9 +9,9 @@ Specification will follow.
OpenKeychain is an OpenPGP implementation for Android. OpenKeychain is an OpenPGP implementation for Android.
For a more detailed description and installation instructions go to http://www.openkeychain.org . For a more detailed description and installation instructions go to http://www.openkeychain.org .
### Travis CI Build Status ### Travis CI Build Status of development branch
[![Build Status](https://travis-ci.org/open-keychain/open-keychain.png?branch=master)](https://travis-ci.org/open-keychain/open-keychain) [![Build Status](https://travis-ci.org/open-keychain/open-keychain.png?branch=development)](https://travis-ci.org/open-keychain/open-keychain)
## How to help the project? ## How to help the project?
@@ -21,12 +21,16 @@ Translations are managed at Transifex, please contribute there at https://www.tr
### Contribute Code ### Contribute Code
1. Lookout for interesting issues on Github. We have tagged issues were we explicitly like to see contributions: https://github.com/open-keychain/open-keychain/labels/help-wanted
2. Read this README, especially the notes about coding style
3. Fork OpenKeychain and contribute code (the best part :sunglasses: )
4. Open a pull request on Github. We will help with occuring problems and merge your changes back into the main project.
5. PROFIT
### For bigger changes
1. Join the development mailinglist at http://groups.google.com/d/forum/openpgp-keychain-dev 1. Join the development mailinglist at http://groups.google.com/d/forum/openpgp-keychain-dev
2. Lookout for interesting issues on Github. We have tagged issues were we explicitly like to see contributions: https://github.com/open-keychain/open-keychain/labels/help-wanted 2. Propose bigger changes and discuss the consequences
3. Tell us about your plans on the mailinglist
4. Read this README, especially the notes about coding style
5. Fork OpenKeychain and contribute code (the best part ;) )
6. Open a pull request on Github. I will help with occuring problems and merge your changes back into the main project.
I am happy about every code contribution and appreciate your effort to help us developing OpenKeychain! I am happy about every code contribution and appreciate your effort to help us developing OpenKeychain!
@@ -36,32 +40,28 @@ Development mailinglist at http://groups.google.com/d/forum/openpgp-keychain-dev
### Build with Gradle ### Build with Gradle
1. Get all external submodules with ``git submodule update --init --recursive`` 1. Clone the project from GitHub
2. Have Android SDK "tools", "platform-tools", and "build-tools" directories in your PATH (http://developer.android.com/sdk/index.html) 2. Get all external submodules with ``git submodule update --init --recursive``
3. Open the Android SDK Manager (shell command: ``android``). 3. Have Android SDK "tools", "platform-tools", and "build-tools" directories in your PATH (http://developer.android.com/sdk/index.html)
Expand the Tools directory and select "Android SDK Build-tools (Version 21.1.1)". 4. Open the Android SDK Manager (shell command: ``android``).
Expand the Tools directory and select "Android SDK Build-tools (Version 21.1.2)".
Expand the Extras directory and install "Android Support Repository" Expand the Extras directory and install "Android Support Repository"
Select everything for the newest SDK Platform (API-Level 21) Select everything for the newest SDK Platform, API 22, and also API 21
4. Export ANDROID_HOME pointing to your Android SDK 5. Export ANDROID_HOME pointing to your Android SDK
5. Execute ``./gradlew build`` 6. Execute ``./gradlew build``
6. You can install the app with ``adb install -r OpenKeychain/build/outputs/apk/OpenKeychain-debug-unaligned.apk`` 7. You can install the app with ``adb install -r OpenKeychain/build/outputs/apk/OpenKeychain-debug-unaligned.apk``
### Run Tests ### Run Tests
1. Use OpenJDK instead of Oracle JDK 1. Use OpenJDK instead of Oracle JDK
3. Execute ``./gradlew test`` 2. Execute ``./gradlew test``
### Build API Demo with Gradle
1. Follow 1-4 from above
2. The example code is available at https://github.com/open-keychain/api-example
3. Execute ``./gradlew build``
### Development with Android Studio ### Development with Android Studio
We are using the newest [Android Studio](http://developer.android.com/sdk/installing/studio.html) for development. Development with Eclipse is currently not possible because we are using the new [project structure](http://developer.android.com/sdk/installing/studio-tips.html). We are using the newest [Android Studio](http://developer.android.com/sdk/installing/studio.html) for development. Development with Eclipse is currently not possible because we are using the new [project structure](http://developer.android.com/sdk/installing/studio-tips.html).
1. Clone the project from Github 1. Clone the project from Github
2. From Android Studio: File -> Import Project -> Select the cloned top folder 2. Get all external submodules with ``git submodule update --init --recursive``
3. From Android Studio: File -> Import Project -> Select the cloned top folder
## Libraries ## Libraries

View File

@@ -7,6 +7,8 @@ buildscript {
// NOTE: Always use fixed version codes not dynamic ones, e.g. 0.7.3 instead of 0.7.+, see README for more information // NOTE: Always use fixed version codes not dynamic ones, e.g. 0.7.3 instead of 0.7.+, see README for more information
classpath 'com.android.tools.build:gradle:1.1.3' classpath 'com.android.tools.build:gradle:1.1.3'
classpath files('gradle-witness.jar') classpath files('gradle-witness.jar')
// bintray dependency to satisfy dependency of openpgp-api lib
classpath 'com.novoda:bintray-release:0.2.7'
} }
} }

Some files were not shown because too many files have changed in this diff Show More