diff options
29 files changed, 671 insertions, 101 deletions
diff --git a/OpenKeychain-Test/build.gradle b/OpenKeychain-Test/build.gradle index 1c87fcb4d..2bf35b3d1 100644 --- a/OpenKeychain-Test/build.gradle +++ b/OpenKeychain-Test/build.gradle @@ -17,7 +17,8 @@ dependencies { testCompile 'junit:junit:4.11' testCompile 'com.google.android:android:4.1.1.4' testCompile('com.squareup:fest-android:1.0.8') { exclude module: 'support-v4' } - testCompile ('org.robolectric:robolectric:2.3') { + testCompile 'org.apache.maven:maven-ant-tasks:2.1.3' + testCompile ('org.robolectric:robolectric:2.4') { exclude module: 'classworlds' exclude module: 'maven-artifact' exclude module: 'maven-artifact-manager' diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index 6fe71540c..2a0779125 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -11,6 +11,18 @@ dependencies { compile 'com.android.support:recyclerview-v7:22.1.0' compile 'com.android.support:cardview-v7:22.1.0' + // UI testing libs + androidTestCompile 'com.android.support.test:runner:0.2' + androidTestCompile 'com.android.support.test:rules:0.2' + androidTestCompile 'com.android.support.test.espresso:espresso-core:2.1' + androidTestCompile 'com.android.support.test.espresso:espresso-contrib:2.1' + + // Temporary workaround for bug: https://code.google.com/p/android-test-kit/issues/detail?id=136 + // from https://github.com/googlesamples/android-testing/blob/master/build.gradle#L21 + configurations.all { + resolutionStrategy.force 'com.android.support:support-annotations:22.1.1' + } + // JCenter etc. compile 'com.eftimoff:android-patternview:1.0.1@aar' compile 'com.journeyapps:zxing-android-embedded:2.3.0@aar' @@ -85,6 +97,8 @@ android { defaultConfig { minSdkVersion 15 targetSdkVersion 22 + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } compileOptions { @@ -135,6 +149,10 @@ android { dexOptions { preDexLibraries = false } + + packagingOptions { + exclude 'LICENSE.txt' + } } // NOTE: This disables Lint! diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/CreateKeyActivityTest.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/CreateKeyActivityTest.java new file mode 100644 index 000000000..c3741fdef --- /dev/null +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/CreateKeyActivityTest.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2015 Dominik Schürmann <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()); + } + +} diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/EditTextMatchers.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/EditTextMatchers.java new file mode 100644 index 000000000..7f2a7953b --- /dev/null +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/EditTextMatchers.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2015 Dominik Schürmann <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"); + } + + }; + } + +} diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index 8c66176fd..6bc61460a 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -741,6 +741,11 @@ android:label="@string/app_name" android:launchMode="singleTop" /> <activity + android:name=".remote.ui.SelectAllowedKeysActivity" + android:exported="false" + android:label="@string/app_name" + android:launchMode="singleTop" /> + <activity android:name=".remote.ui.AppSettingsActivity" android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:exported="false"> diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java index d26ccbe57..710dbf8aa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java @@ -24,6 +24,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.res.Resources; +import android.graphics.Bitmap; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.os.Build; @@ -40,6 +41,8 @@ import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.TlsHelper; import java.security.Security; +import java.util.HashMap; + public class KeychainApplication extends Application { @@ -100,6 +103,17 @@ public class KeychainApplication extends Application { checkConsolidateRecovery(); } + public static HashMap<String,Bitmap> qrCodeCache = new HashMap<>(); + + @Override + public void onTrimMemory(int level) { + super.onTrimMemory(level); + + if (level >= TRIM_MEMORY_UI_HIDDEN) { + qrCodeCache.clear(); + } + } + /** * Restart consolidate process if it has been interruped before */ diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java index 90ec3053f..bf2349734 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java @@ -1,3 +1,21 @@ +/* + * Copyright (C) 2015 Dominik Schürmann <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; @@ -32,7 +50,7 @@ public class PgpCertifyOperation { OperationLog log, int indent, CertifyAction action, - Map<ByteBuffer,byte[]> signedHashes, + Map<ByteBuffer, byte[]> signedHashes, Date creationTimestamp) { if (!secretKey.isMasterKey()) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java index 8ecb30cdd..9073e81b9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java @@ -178,13 +178,20 @@ public class PgpSignEncryptOperation extends BaseOperation { case PIN: case PATTERN: case PASSPHRASE: { - if (cryptoInput.getPassphrase() == null) { + Passphrase localPassphrase = cryptoInput.getPassphrase(); + if (localPassphrase == null) { + try { + localPassphrase = getCachedPassphrase(signingKeyRing.getMasterKeyId(), signingKey.getKeyId()); + } catch (PassphraseCacheInterface.NoSecretKeyException ignored) { + } + } + if (localPassphrase == null) { log.add(LogType.MSG_PSE_PENDING_PASSPHRASE, indent + 1); return new PgpSignEncryptResult(log, RequiredInputParcel.createRequiredSignPassphrase( signingKeyRing.getMasterKeyId(), signingKey.getKeyId(), cryptoInput.getSignatureTime())); } - if (!signingKey.unlock(cryptoInput.getPassphrase())) { + if (!signingKey.unlock(localPassphrase)) { log.add(LogType.MSG_PSE_ERROR_BAD_PASSPHRASE, indent); return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java index a65d222da..6dd4a1633 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java @@ -31,10 +31,12 @@ import android.provider.OpenableColumns; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.util.DatabaseUtil; +import org.sufficientlysecure.keychain.util.Log; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.util.UUID; public class TemporaryStorageProvider extends ContentProvider { @@ -44,7 +46,7 @@ public class TemporaryStorageProvider extends ContentProvider { private static final String COLUMN_NAME = "name"; private static final String COLUMN_TIME = "time"; private static final Uri BASE_URI = Uri.parse("content://org.sufficientlysecure.keychain.tempstorage/"); - private static final int DB_VERSION = 1; + private static final int DB_VERSION = 2; public static Uri createFile(Context context, String targetName) { ContentValues contentValues = new ContentValues(); @@ -66,7 +68,7 @@ public class TemporaryStorageProvider extends ContentProvider { @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_FILES + " (" + - COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + COLUMN_ID + " TEXT PRIMARY KEY, " + COLUMN_NAME + " TEXT, " + COLUMN_TIME + " INTEGER" + ");"); @@ -74,7 +76,17 @@ public class TemporaryStorageProvider extends ContentProvider { @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - + Log.d(Constants.TAG, "Upgrading files db from " + oldVersion + " to " + newVersion); + + switch (oldVersion) { + case 1: + db.execSQL("DROP TABLE IF EXISTS files"); + db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_FILES + " (" + + COLUMN_ID + " TEXT PRIMARY KEY, " + + COLUMN_NAME + " TEXT, " + + COLUMN_TIME + " INTEGER" + + ");"); + } } } @@ -82,13 +94,13 @@ public class TemporaryStorageProvider extends ContentProvider { private File getFile(Uri uri) throws FileNotFoundException { try { - return getFile(Integer.parseInt(uri.getLastPathSegment())); + return getFile(uri.getLastPathSegment()); } catch (NumberFormatException e) { throw new FileNotFoundException(); } } - private File getFile(int id) { + private File getFile(String id) { return new File(getContext().getCacheDir(), "temp/" + id); } @@ -133,13 +145,15 @@ public class TemporaryStorageProvider extends ContentProvider { if (!values.containsKey(COLUMN_TIME)) { values.put(COLUMN_TIME, System.currentTimeMillis()); } + String uuid = UUID.randomUUID().toString(); + values.put(COLUMN_ID, uuid); int insert = (int) db.getWritableDatabase().insert(TABLE_FILES, null, values); try { - getFile(insert).createNewFile(); + getFile(uuid).createNewFile(); } catch (IOException e) { return null; } - return Uri.withAppendedPath(BASE_URI, Long.toString(insert)); + return Uri.withAppendedPath(BASE_URI, uuid); } @Override @@ -152,7 +166,7 @@ public class TemporaryStorageProvider extends ContentProvider { selectionArgs, null, null, null); if (files != null) { while (files.moveToNext()) { - getFile(files.getInt(0)).delete(); + getFile(files.getString(0)).delete(); } files.close(); return db.getWritableDatabase().delete(TABLE_FILES, selection, selectionArgs); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index c51edf59c..badc3c131 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -34,6 +34,7 @@ import org.openintents.openpgp.util.OpenPgpApi; import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel; import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult; import org.sufficientlysecure.keychain.pgp.PgpConstants; @@ -47,6 +48,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity; +import org.sufficientlysecure.keychain.remote.ui.SelectAllowedKeysActivity; import org.sufficientlysecure.keychain.remote.ui.SelectSignKeyIdActivity; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; @@ -205,6 +207,18 @@ public class OpenPgpService extends RemoteService { PendingIntent.FLAG_CANCEL_CURRENT); } + private PendingIntent getSelectAllowedKeysIntent(Intent data) { + // If signature is unknown we return an _additional_ PendingIntent + // to retrieve the missing key + Intent intent = new Intent(getBaseContext(), SelectAllowedKeysActivity.class); + intent.putExtra(SelectAllowedKeysActivity.EXTRA_SERVICE_INTENT, data); + intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(getCurrentCallingPackage())); + + return PendingIntent.getActivity(getBaseContext(), 0, + intent, + PendingIntent.FLAG_CANCEL_CURRENT); + } + private PendingIntent getShowKeyPendingIntent(long masterKeyId) { Intent intent = new Intent(getBaseContext(), ViewKeyActivity.class); intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); @@ -476,13 +490,12 @@ public class OpenPgpService extends RemoteService { } String currentPkg = getCurrentCallingPackage(); - Set<Long> allowedKeyIds; + Set<Long> allowedKeyIds = mProviderHelper.getAllowedKeyIdsForApp( + KeychainContract.ApiAllowedKeys.buildBaseUri(currentPkg)); + if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) < 7) { - allowedKeyIds = mProviderHelper.getAllKeyIdsForApp( - ApiAccounts.buildBaseUri(currentPkg)); - } else { - allowedKeyIds = mProviderHelper.getAllowedKeyIdsForApp( - KeychainContract.ApiAllowedKeys.buildBaseUri(currentPkg)); + allowedKeyIds.addAll(mProviderHelper.getAllKeyIdsForApp( + ApiAccounts.buildBaseUri(currentPkg))); } long inputLength = is.available(); @@ -575,6 +588,15 @@ public class OpenPgpService extends RemoteService { return result; } else { LogEntryParcel errorMsg = pgpResult.getLog().getLast(); + + if (errorMsg.mType == OperationResult.LogType.MSG_DC_ERROR_NO_KEY) { + // allow user to select allowed keys + Intent result = new Intent(); + result.putExtra(OpenPgpApi.RESULT_INTENT, getSelectAllowedKeysIntent(data)); + result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED); + return result; + } + throw new Exception(getString(errorMsg.mType.getMsgId())); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java index f312c0d44..5facde64f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java @@ -45,6 +45,7 @@ import org.sufficientlysecure.keychain.util.Log; import java.util.ArrayList; +// TODO: make extensible BaseRemoteServiceActivity and extend these cases from it public class RemoteServiceActivity extends BaseActivity { public static final String ACTION_REGISTER = Constants.INTENT_PREFIX + "API_ACTIVITY_REGISTER"; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectAllowedKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectAllowedKeysActivity.java new file mode 100644 index 000000000..767106ff0 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectAllowedKeysActivity.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2015 Dominik Schürmann <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(); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java index 078135772..aeae8a1ad 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java @@ -208,6 +208,7 @@ public class CreateKeyEmailFragment extends Fragment { Bundle data = message.getData(); String email = data.getString(AddEmailDialogFragment.MESSAGE_DATA_EMAIL); + if (checkEmail(email, true)) { // add new user id mEmailAdapter.add(email); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java index 651b56ab0..9c51893ce 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java @@ -231,6 +231,7 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements KeychainContract.KeyRings.IS_REVOKED, KeychainContract.KeyRings.IS_EXPIRED, KeychainContract.KeyRings.VERIFIED, + KeychainContract.KeyRings.HAS_ANY_SECRET, }; @SuppressWarnings("unused") @@ -239,6 +240,7 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements static final int INDEX_IS_REVOKED = 3; static final int INDEX_IS_EXPIRED = 4; static final int INDEX_VERIFIED = 5; + static final int INDEX_HAS_ANY_SECRET = 6; @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { @@ -283,6 +285,7 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements boolean isRevoked = data.getInt(INDEX_IS_REVOKED) != 0; boolean isExpired = data.getInt(INDEX_IS_EXPIRED) != 0; boolean isVerified = data.getInt(INDEX_VERIFIED) > 0; + boolean isYours = data.getInt(INDEX_HAS_ANY_SECRET) != 0; if (isRevoked) { mSignatureText.setText(R.string.decrypt_result_signature_revoked_key); @@ -302,6 +305,16 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements 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); + + onVerifyLoaded(true); + } else if (isVerified) { mSignatureText.setText(R.string.decrypt_result_signature_certified); KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.VERIFIED); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index 96ce101b5..1355bd3e6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -240,7 +240,7 @@ public class KeyListFragment extends LoaderFragment } case R.id.menu_key_list_multi_select_all: { // select all - for (int i = 0; i < mStickyList.getCount(); i++) { + for (int i = 0; i < mAdapter.getCount(); i++) { mStickyList.setItemChecked(i, true); } break; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java index 6f19fc6ed..eef44a94b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java @@ -27,6 +27,7 @@ import android.database.Cursor; import android.graphics.PorterDuff; import android.support.v4.widget.CursorAdapter; import android.text.format.DateFormat; +import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -88,6 +89,7 @@ public class KeyAdapter extends CursorAdapter { public Long mMasterKeyId; public TextView mMainUserId; public TextView mMainUserIdRest; + public TextView mCreationDate; public ImageView mStatus; public View mSlinger; public ImageButton mSlingerButton; @@ -98,6 +100,7 @@ public class KeyAdapter extends CursorAdapter { mStatus = (ImageView) view.findViewById(R.id.key_list_item_status_icon); mSlinger = view.findViewById(R.id.key_list_item_slinger_view); mSlingerButton = (ImageButton) view.findViewById(R.id.key_list_item_slinger_button); + mCreationDate = (TextView) view.findViewById(R.id.key_list_item_creation); } public void setData(Context context, Cursor cursor, Highlighter highlighter) { @@ -125,7 +128,7 @@ public class KeyAdapter extends CursorAdapter { boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0; boolean isExpired = cursor.getInt(INDEX_IS_EXPIRED) != 0; boolean isVerified = cursor.getInt(INDEX_VERIFIED) > 0; - // boolean hasDuplicate = cursor.getInt(INDEX_HAS_DUPLICATE_USER_ID) == 1; + boolean hasDuplicate = cursor.getInt(INDEX_HAS_DUPLICATE_USER_ID) != 0; mMasterKeyId = masterKeyId; @@ -165,6 +168,21 @@ public class KeyAdapter extends CursorAdapter { mMainUserId.setTextColor(context.getResources().getColor(R.color.black)); mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black)); } + + if (hasDuplicate) { + String dateTime = DateUtils.formatDateTime(context, + cursor.getLong(INDEX_CREATION) * 1000, + DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_SHOW_YEAR + | DateUtils.FORMAT_ABBREV_MONTH); + + mCreationDate.setText(context.getString(R.string.label_key_created, + dateTime)); + mCreationDate.setVisibility(View.VISIBLE); + } else { + mCreationDate.setVisibility(View.GONE); + } + } } @@ -262,20 +280,6 @@ public class KeyAdapter extends CursorAdapter { } } - public boolean hasDuplicate() { - return mHasDuplicate; - } - - public String getCreationDate(Context context) { - Calendar creationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - creationCal.setTime(mCreation); - // convert from UTC to time zone of device - creationCal.setTimeZone(TimeZone.getDefault()); - - return context.getString(R.string.label_creation) + ": " - + DateFormat.getDateFormat(context).format(creationCal.getTime()); - } - } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java index 3308a4500..a6cb52977 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java @@ -20,7 +20,7 @@ package org.sufficientlysecure.keychain.ui.adapter; import android.content.Context; import android.database.Cursor; import android.support.v4.widget.CursorAdapter; -import android.text.format.DateFormat; +import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -36,10 +36,6 @@ import org.sufficientlysecure.keychain.ui.util.Highlighter; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; -import java.util.Calendar; -import java.util.Date; -import java.util.TimeZone; - /** * Yes this class is abstract! @@ -138,14 +134,13 @@ abstract public class SelectKeyCursorAdapter extends CursorAdapter { boolean duplicate = cursor.getLong(mIndexDuplicateUserId) > 0; if (duplicate) { - Date creationDate = new Date(cursor.getLong(mIndexCreation) * 1000); - Calendar creationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - creationCal.setTime(creationDate); - // convert from UTC to time zone of device - creationCal.setTimeZone(TimeZone.getDefault()); - - h.creation.setText(context.getString(R.string.label_creation) + ": " - + DateFormat.getDateFormat(context).format(creationCal.getTime())); + String dateTime = DateUtils.formatDateTime(context, + cursor.getLong(mIndexCreation) * 1000, + DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_SHOW_YEAR + | DateUtils.FORMAT_ABBREV_MONTH); + + h.creation.setText(context.getString(R.string.label_key_created, dateTime)); h.creation.setVisibility(View.VISIBLE); } else { h.creation.setVisibility(View.GONE); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java index ae66b59d4..91a7d361a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java @@ -475,7 +475,7 @@ public class KeyFormattingUtils { statusIcon.setImageDrawable( context.getResources().getDrawable(R.drawable.status_signature_unknown_cutout_24dp)); if (color == KeyFormattingUtils.DEFAULT_COLOR) { - color = R.color.android_orange_light; + color = R.color.android_red_light; } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/QrCodeUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/QrCodeUtils.java index b8d4ea7d2..5f71abdab 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/QrCodeUtils.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/QrCodeUtils.java @@ -29,6 +29,7 @@ import com.google.zxing.qrcode.QRCodeWriter; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.KeychainApplication; import org.sufficientlysecure.keychain.util.Log; import java.util.Hashtable; @@ -40,36 +41,45 @@ public class QrCodeUtils { /** * Generate Bitmap with QR Code based on input. - * - * @param input - * @param size * @return QR Code as Bitmap */ public static Bitmap getQRCodeBitmap(final String input, final int size) { + try { - final Hashtable<EncodeHintType, Object> hints = new Hashtable<>(); - hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M); - final BitMatrix result = new QRCodeWriter().encode(input, BarcodeFormat.QR_CODE, size, - size, hints); - - final int width = result.getWidth(); - final int height = result.getHeight(); - final int[] pixels = new int[width * height]; - - for (int y = 0; y < height; y++) { - final int offset = y * width; - for (int x = 0; x < width; x++) { - pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.TRANSPARENT; + + // the qrCodeCache is handled in KeychainApplication so we can + // properly react to onTrimMemory calls + Bitmap bitmap = KeychainApplication.qrCodeCache.get(input); + if (bitmap == null) { + + Hashtable<EncodeHintType, Object> hints = new Hashtable<>(); + hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M); + BitMatrix result = new QRCodeWriter().encode(input, BarcodeFormat.QR_CODE, size, + size, hints); + + int width = result.getWidth(); + int height = result.getHeight(); + int[] pixels = new int[width * height]; + + for (int y = 0; y < height; y++) { + final int offset = y * width; + for (int x = 0; x < width; x++) { + pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.TRANSPARENT; + } } + + bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + bitmap.setPixels(pixels, 0, width, 0, 0, width, height); + + KeychainApplication.qrCodeCache.put(input, bitmap); } - final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - bitmap.setPixels(pixels, 0, width, 0, 0, width, height); return bitmap; - } catch (final WriterException e) { + } catch (WriterException e) { Log.e(Constants.TAG, "QrCodeUtils", e); return null; } + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java index 3d2e8b9df..63a1aade9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java @@ -101,7 +101,7 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView /*if (completionText.startsWith("0x")) { }*/ - return null; + return ""; } @Override @@ -110,7 +110,7 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView if (getContext() instanceof FragmentActivity) { mLoaderManager = ((FragmentActivity) getContext()).getSupportLoaderManager(); - mLoaderManager.initLoader(hashCode(), null, this); + mLoaderManager.initLoader(0, null, this); } else { Log.e(Constants.TAG, "EncryptKeyCompletionView must be attached to a FragmentActivity, this is " + getContext().getClass()); } @@ -136,7 +136,7 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView where += " AND " + KeyRings.USER_ID + " LIKE ?"; return new CursorLoader(getContext(), baseUri, KeyAdapter.PROJECTION, where, - new String[] { "%" + query + "%" }, null); + new String[]{"%" + query + "%"}, null); } mAdapter.setSearchQuery(null); @@ -155,6 +155,14 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView } @Override + public void showDropDown() { + if (mAdapter == null || mAdapter.getCursor() == null || mAdapter.getCursor().isClosed()) { + return; + } + super.showDropDown(); + } + + @Override public void onFocusChanged(boolean hasFocus, int direction, Rect previous) { super.onFocusChanged(hasFocus, direction, previous); if (hasFocus) { @@ -171,7 +179,7 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView } Bundle args = new Bundle(); args.putString(ARG_QUERY, text.subSequence(start, end).toString()); - mLoaderManager.restartLoader(hashCode(), args, this); + mLoaderManager.restartLoader(0, args, this); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java index fc5ecd76a..aecc81604 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java @@ -26,6 +26,7 @@ import android.support.v4.content.Loader; import android.support.v4.widget.CursorAdapter; import android.support.v7.widget.AppCompatSpinner; import android.text.format.DateFormat; +import android.text.format.DateUtils; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; @@ -167,14 +168,13 @@ public abstract class KeySpinner extends AppCompatSpinner implements LoaderManag boolean duplicate = cursor.getLong(mIndexDuplicate) > 0; if (duplicate) { - Date creationDate = new Date(cursor.getLong(mIndexCreationDate) * 1000); - Calendar creationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - creationCal.setTime(creationDate); - // convert from UTC to time zone of device - creationCal.setTimeZone(TimeZone.getDefault()); - - vDuplicate.setText(context.getString(R.string.label_creation) + ": " - + DateFormat.getDateFormat(context).format(creationCal.getTime())); + String dateTime = DateUtils.formatDateTime(context, + cursor.getLong(mIndexCreationDate) * 1000, + DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_SHOW_YEAR + | DateUtils.FORMAT_ABBREV_MONTH); + + vDuplicate.setText(context.getString(R.string.label_key_created, dateTime)); vDuplicate.setVisibility(View.VISIBLE); } else { vDuplicate.setVisibility(View.GONE); diff --git a/OpenKeychain/src/main/res/layout/api_remote_select_allowed_keys.xml b/OpenKeychain/src/main/res/layout/api_remote_select_allowed_keys.xml new file mode 100644 index 000000000..e052ff333 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/api_remote_select_allowed_keys.xml @@ -0,0 +1,38 @@ +<?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>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/create_yubikey_wait_fragment.xml b/OpenKeychain/src/main/res/layout/create_yubikey_wait_fragment.xml index c7f9821eb..4e4b53118 100644 --- a/OpenKeychain/src/main/res/layout/create_yubikey_wait_fragment.xml +++ b/OpenKeychain/src/main/res/layout/create_yubikey_wait_fragment.xml @@ -22,8 +22,7 @@ android:layout_marginTop="16dp" android:layout_marginLeft="8dp" android:textAppearance="?android:attr/textAppearanceMedium" - android:text="Hold Yubikey against device dawg" - /> + android:text="@string/yubikey_create" /> <ImageView android:layout_width="match_parent" diff --git a/OpenKeychain/src/main/res/layout/key_list_item.xml b/OpenKeychain/src/main/res/layout/key_list_item.xml index fbe5f1326..6078b898f 100644 --- a/OpenKeychain/src/main/res/layout/key_list_item.xml +++ b/OpenKeychain/src/main/res/layout/key_list_item.xml @@ -26,7 +26,7 @@ android:id="@+id/key_list_item_name" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/label_main_user_id" + tools:text="@string/label_main_user_id" android:textAppearance="?android:attr/textAppearanceMedium" /> <TextView @@ -35,8 +35,20 @@ android:layout_height="wrap_content" android:singleLine="true" android:ellipsize="end" - android:text="user@example.com" + tools:text="user@example.com" android:textAppearance="?android:attr/textAppearanceSmall" /> + + <TextView + android:id="@+id/key_list_item_creation" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:singleLine="true" + android:textAppearance="?android:attr/textAppearanceSmall" + android:visibility="gone" + tools:visibility="visible" + tools:text="Created on 10/10/2010 10:00" + /> + </LinearLayout> <LinearLayout diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index f77570a88..72ba46702 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -41,7 +41,7 @@ <!-- section --> <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_should_you_trust">"Should you trust this key?"</string> <string name="section_proof_details">Proof verification</string> @@ -137,6 +137,7 @@ <string name="label_file_compression">"File compression"</string> <string name="label_keyservers">"Keyservers"</string> <string name="label_key_id">"Key ID"</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_usage">"Usage"</string> @@ -287,6 +288,7 @@ <string name="decrypt_result_no_signature">"Not Signed"</string> <string name="decrypt_result_invalid_signature">"Invalid signature!"</string> <string name="decrypt_result_signature_uncertified">"Signed by <b>unconfirmed</b> key"</string> + <string name="decrypt_result_signature_secret">"Signed by your key"</string> <string name="decrypt_result_signature_certified">"Signed by confirmed key"</string> <string name="decrypt_result_signature_expired_key">"Signed by <b>expired</b> key!"</string> <string name="decrypt_result_signature_revoked_key">"Signed by <b>revoked</b> key!"</string> @@ -502,7 +504,7 @@ <string name="api_settings_delete_account">"Delete account"</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_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_allowed_keys">"Allowed Keys"</string> <string name="api_settings_settings">"Settings"</string> @@ -520,6 +522,7 @@ <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_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 --> <string name="share_qr_code_dialog_title">"Share with QR Code"</string> @@ -1278,11 +1281,12 @@ <string name="yubikey_serno">"Serial No: %s"</string> <string name="yubikey_key_holder">"Key holder: "</string> <string name="yubikey_key_holder_unset">"Key holder: <unset>"</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_partly">"Yubikey matches, partly 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_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="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_pin_nodefault">Default PIN was rejected!</string> @@ -3,9 +3,9 @@ OpenKeychain is an OpenPGP implementation for Android. 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 -[](https://travis-ci.org/open-keychain/open-keychain) +[](https://travis-ci.org/open-keychain/open-keychain) ## How to help the project? @@ -30,19 +30,20 @@ Development mailinglist at http://groups.google.com/d/forum/openpgp-keychain-dev ### Build with Gradle -1. Get all external submodules with ``git submodule update --init --recursive`` -2. Have Android SDK "tools", "platform-tools", and "build-tools" directories in your PATH (http://developer.android.com/sdk/index.html) -3. Open the Android SDK Manager (shell command: ``android``). -Expand the Tools directory and select "Android SDK Build-tools (Version 21.1.1)". +1. Clone the project from GitHub +2. Get all external submodules with ``git submodule update --init --recursive`` +3. Have Android SDK "tools", "platform-tools", and "build-tools" directories in your PATH (http://developer.android.com/sdk/index.html) +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" -Select everything for the newest SDK Platform (API-Level 21) -4. Export ANDROID_HOME pointing to your Android SDK -5. Execute ``./gradlew build`` -6. You can install the app with ``adb install -r OpenKeychain/build/outputs/apk/OpenKeychain-debug-unaligned.apk`` +Select everything for the newest SDK Platform, API 22, and also API 21 +5. Export ANDROID_HOME pointing to your Android SDK +6. Execute ``./gradlew build`` +7. You can install the app with ``adb install -r OpenKeychain/build/outputs/apk/OpenKeychain-debug-unaligned.apk`` ### Run Tests 1. Use OpenJDK instead of Oracle JDK -3. Execute ``./gradlew test`` +2. Execute ``./gradlew test`` ### Build API Demo with Gradle @@ -55,7 +56,8 @@ Select everything for the newest SDK Platform (API-Level 21) 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 -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 diff --git a/extern/snackbar b/extern/snackbar -Subproject 97cda0806d9ead9a3aacf3d4f7275ac0e38d960 +Subproject 12e1e8b7e88a09249aed68a48a6c4d06c401e00 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar Binary files differindex 085a1cdc2..cb5ccd9f1 100644 --- a/gradle/wrapper/gradle-wrapper.jar +++ b/gradle/wrapper/gradle-wrapper.jar diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8308df30c..6e00d9230 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -4,3 +4,4 @@ distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-2.3-bin.zip +distributionSha256Sum=010dd9f31849abc3d5644e282943b1c1c355f8e2635c5789833979ce590a3774
\ No newline at end of file |