mirror of
https://github.com/moparisthebest/open-keychain
synced 2024-11-30 12:32:17 -05:00
Merge pull request #1352 from open-keychain/v/instrument
instrumentation branch
This commit is contained in:
commit
300fd8e0f2
48
.travis.yml
48
.travis.yml
@ -1,18 +1,36 @@
|
|||||||
language: java
|
language: android
|
||||||
jdk: openjdk7
|
jdk: openjdk7
|
||||||
before_install:
|
# container based build, we don't need root anyways
|
||||||
# Install base Android SDK
|
sudo: false
|
||||||
- sudo apt-get update -qq
|
# env:
|
||||||
- if [ `uname -m` = x86_64 ]; then sudo apt-get install -qq --force-yes libgd2-xpm lib32z1 lib32stdc++6; fi
|
# global:
|
||||||
- wget http://dl.google.com/android/android-sdk_r24.1.2-linux.tgz
|
# - ANDROID_API_LEVEL=21
|
||||||
- tar xzf android-sdk_r24.1.2-linux.tgz
|
# - ANDROID_ABI=armeabi-v7a
|
||||||
- export ANDROID_HOME=$PWD/android-sdk-linux
|
# - ADB_INSTALL_TIMEOUT=8 # minutes (2 minutes by default)
|
||||||
- export PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools
|
android:
|
||||||
|
components:
|
||||||
|
- build-tools-22.0.1
|
||||||
|
- build-tools-21.1.2
|
||||||
|
- build-tools-21.1.1
|
||||||
|
- build-tools-19.1.0
|
||||||
|
- android-22
|
||||||
|
- android-21
|
||||||
|
- android-19
|
||||||
|
- platform-tools
|
||||||
|
- extra-android-support
|
||||||
|
- extra-android-m2repository
|
||||||
|
licenses:
|
||||||
|
- 'android-sdk-preview-license-52d11cd2'
|
||||||
|
- 'android-sdk-license-.+'
|
||||||
|
- 'google-gdk-license-.+'
|
||||||
|
|
||||||
|
# doesn't work, travis is just too slow
|
||||||
|
# before_script:
|
||||||
|
# - echo no | android create avd --force -n test -t android-$ANDROID_API_LEVEL --abi $ANDROID_ABI
|
||||||
|
# - emulator -avd test -no-skin -no-audio -no-window &
|
||||||
|
# - ./tools/android-wait-for-emulator
|
||||||
|
# - adb shell input keyevent 82 &
|
||||||
|
|
||||||
# Install required Android components.
|
|
||||||
#- echo "y" | android update sdk -a --filter build-tools-19.1.0,android-19,platform-tools,extra-android-support,extra-android-m2repository --no-ui --force
|
|
||||||
- ( sleep 5 && while [ 1 ]; do sleep 1; echo y; done ) | android update sdk --no-ui --all --force --filter build-tools-22.0.1,build-tools-21.1.2,build-tools-21.1.1,build-tools-19.1.0,android-22,android-21,android-19,platform-tools,extra-android-support,extra-android-m2repository
|
|
||||||
install: echo "Installation done"
|
|
||||||
script:
|
script:
|
||||||
- ./gradlew testDebug jacocoTestReport coveralls --info
|
# - ./gradlew connectedAndroidTest
|
||||||
|
- ./gradlew testDebug jacocoTestReport coveralls
|
||||||
|
@ -21,10 +21,14 @@ dependencies {
|
|||||||
testCompile 'org.robolectric:robolectric:3.0-rc3'
|
testCompile 'org.robolectric:robolectric:3.0-rc3'
|
||||||
|
|
||||||
// UI testing with Espresso
|
// UI testing with Espresso
|
||||||
androidTestCompile 'com.android.support.test:runner:0.2'
|
androidTestCompile 'com.android.support.test:runner:0.3'
|
||||||
androidTestCompile 'com.android.support.test:rules:0.2'
|
androidTestCompile 'com.android.support.test:rules:0.3'
|
||||||
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.1'
|
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2'
|
||||||
androidTestCompile 'com.android.support.test.espresso:espresso-contrib:2.1'
|
androidTestCompile ('com.android.support.test.espresso:espresso-contrib:2.2') {
|
||||||
|
exclude group: 'com.android.support', module: 'appcompat'
|
||||||
|
exclude group: 'com.android.support', module: 'support-v4'
|
||||||
|
exclude module: 'recyclerview-v7'
|
||||||
|
}
|
||||||
|
|
||||||
// Temporary workaround for bug: https://code.google.com/p/android-test-kit/issues/detail?id=136
|
// 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
|
// from https://github.com/googlesamples/android-testing/blob/master/build.gradle#L21
|
||||||
@ -110,7 +114,13 @@ android {
|
|||||||
versionCode 32300
|
versionCode 32300
|
||||||
versionName "3.2.3"
|
versionName "3.2.3"
|
||||||
applicationId "org.sufficientlysecure.keychain"
|
applicationId "org.sufficientlysecure.keychain"
|
||||||
|
// the androidjunitrunner is broken regarding coverage, see here:
|
||||||
|
// https://code.google.com/p/android/issues/detail?id=170607
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
|
// this workaround runner fixes the coverage problem, BUT doesn't work
|
||||||
|
// with android studio single test execution. use it to generate coverage
|
||||||
|
// data, but keep the other one otherwis
|
||||||
|
// testInstrumentationRunner "org.sufficientlysecure.keychain.JacocoWorkaroundJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@ -139,9 +149,8 @@ android {
|
|||||||
// Reference them in .xml files.
|
// Reference them in .xml files.
|
||||||
resValue "string", "account_type", "org.sufficientlysecure.keychain.debug.account"
|
resValue "string", "account_type", "org.sufficientlysecure.keychain.debug.account"
|
||||||
|
|
||||||
// Disabled: only works for androidTest not test!
|
|
||||||
// Enable code coverage (Jacoco)
|
// Enable code coverage (Jacoco)
|
||||||
//testCoverageEnabled true
|
testCoverageEnabled true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,6 +203,8 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// apply plugin: 'spoon'
|
||||||
|
|
||||||
task jacocoTestReport(type:JacocoReport) {
|
task jacocoTestReport(type:JacocoReport) {
|
||||||
group = "Reporting"
|
group = "Reporting"
|
||||||
description = "Generate Jacoco coverage reports"
|
description = "Generate Jacoco coverage reports"
|
||||||
@ -212,7 +223,10 @@ task jacocoTestReport(type:JacocoReport) {
|
|||||||
"${buildDir}/generated/source/buildConfig/debug",
|
"${buildDir}/generated/source/buildConfig/debug",
|
||||||
"${buildDir}/generated/source/r/debug"
|
"${buildDir}/generated/source/r/debug"
|
||||||
])
|
])
|
||||||
executionData = files("${buildDir}/jacoco/testDebug.exec")
|
executionData = files([
|
||||||
|
"${buildDir}/jacoco/testDebug.exec",
|
||||||
|
"${buildDir}/outputs/code-coverage/connected/coverage.ec"
|
||||||
|
])
|
||||||
|
|
||||||
reports {
|
reports {
|
||||||
xml.enabled = true
|
xml.enabled = true
|
||||||
|
BIN
OpenKeychain/build/outputs/code-coverage/connected/coverage.ec
Normal file
BIN
OpenKeychain/build/outputs/code-coverage/connected/coverage.ec
Normal file
Binary file not shown.
1032
OpenKeychain/src/androidTest/assets/valodim.pub.asc
Normal file
1032
OpenKeychain/src/androidTest/assets/valodim.pub.asc
Normal file
File diff suppressed because it is too large
Load Diff
156
OpenKeychain/src/androidTest/assets/x.sec.asc
Normal file
156
OpenKeychain/src/androidTest/assets/x.sec.asc
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
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
|
||||||
|
=Sxs8
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
|
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||||
|
|
||||||
|
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
|
||||||
|
lK6EVIGGeJVSi9UaCPwsIsMv6am5z34dTNhYkoaSz54ZjQ5350mOCH7BdCTStNOR
|
||||||
|
jzSQqkiNEQM5W49GGzDk5hvrd9uJpjCiuY/Hile/aaK05g0BfLdg2lFeXky85tgt
|
||||||
|
C6Cmq3KOmKPLIA1uwcpbAKWQv+/Gdtx9FcXfTs1AZLCGzeAqlK6i9UR50SQjZAEj
|
||||||
|
RWdwZ3BpZDpAZG5zOnRlc3QubXVnZW5ndWlsZC5jb22JAhwEEwEKAAYFglT0q7MA
|
||||||
|
CgkQnWBNLzEHFqOc8Q/6Avcxqwv0Zxe3Rz3dCRCxFrLwgwKykz5C39et+hRIlvjV
|
||||||
|
Xiep7K6Zf+RGZjv94xkmfEgmCfxpaZGOI+FqxQwF95eabhECbAcSQU/qfDbzx0ci
|
||||||
|
UqBFoAzB+zji2ErE8pOSxwuJKpuatGEtQtbzjSkhmSC04uaEfa2JprvD+GAMIyAd
|
||||||
|
/BlpKVCQQN+8B5/uLrWK010GUvR7QJ6XM0vRRzk/pv82+fm5aWssgdl687Hx2zCG
|
||||||
|
bDPbkPsplA+H3UmkCqguTmGPf8MBtXF5QUr9m32j9u76KPnupJ9GJ/ceUvJ0f3wU
|
||||||
|
Vx62otsYO3X8SJ5re2mqMrleB/4c6mlCjYyk4I/rjE16MZacrHozvIUD/KIjgCF7
|
||||||
|
i4/35JdqvQU8sZcB/Jz+lKtE33h1j6JelY8JKU1E0A7KvWbE8K/D0wwLnBid8stJ
|
||||||
|
ZQw9i/2tDKvWm9jLF7avQWVrokCRHLmcydHVF2ivBp94xzJCLeeWUFJLTDOZqC6z
|
||||||
|
VbaqpFts5HwnkJeSSNvD29XS9cWcIer3eaz0RueGvms3NWd0qfFUKYqF+1buAXPx
|
||||||
|
DPe0brp5JYD/pWtG99Q19HlxQGlU4BGrGpjPs4+saDQuR/D4k9Bfy/9Ywh8zomyP
|
||||||
|
+SgmY5vbL+HSsn6JMGh88KgY40ojOU4rlzxuFKZT/bspfXEFpxOA31CGW6UhtmzR
|
||||||
|
KypkABLWh3BncGlkK2Nvb2tpZTpAZG5zOnRlc3QubXVnZW5ndWlsZC5jb22JAhwE
|
||||||
|
EwEKAAYFglT3TUwACgkQnWBNLzEHFqMcoA//dYN5R8GS6rnS/cTxIHGoMdeiY+dw
|
||||||
|
05xxE9LX8NjDFWnklgTw2wVbauWZ4mLLhGGbgyem1QVRM8wF9g5we9Ohav/cbPTL
|
||||||
|
mKTmiI8LmEBdYI+s2R22AG6AEHIv7T7DY9YpCrAEhz15/7toiHbx0qfrOvvUC74N
|
||||||
|
ckqpjP+M6pjcgkbMGTJ3E49LLzdY0+b05H/N+EI6gJMMDL7RMbFiCNYh2LiTjINF
|
||||||
|
aEB4X4aolG4N/VEpnE8vUyPCFfQu/id1H5XewQJFWHodEgEZhi2LyRTRD2N1tiNT
|
||||||
|
6C56y3LK+xZAE5oZE/6AZBj59h0dvZhQrqMxyd0/FtGieNpFjkFxzQk0BgCoW/9b
|
||||||
|
JHuF3hJZcjxmDe74ZXvHxludQ7B+9cFW9miFvJymhPtZU49GFDYJDk+5LYTlBqgD
|
||||||
|
p9R8fapQBrGW+d8xE8v0TWy8gQVcy1/Uao7qHPMlZXpDDn0c4NPLnrxK2WgzohWe
|
||||||
|
WiUl/F/v5oRDH4qubEmpVVaVtnm7mDNBr4xqTQ3kXqbzDhV+3IdPRbpGKc2HcgZu
|
||||||
|
Trj65djMBSEnptp19euvFM/TCSM88+M0L8Ep88LNiFmfIYsK4XCU6oSjU2rqozNI
|
||||||
|
c6XFHTzJxfQws0R9ayq7YJXxgYthOF6M6vwlKPJczcA0c0H4Bnn955gAZRzleDFN
|
||||||
|
ur8ymfK2IHNpl/DRPDtkABLWh3BncGlkK2Nvb2tpZTpnZW5lcmljQGh0dHBzOi8v
|
||||||
|
bXVnZW5ndWlsZC5jb20vcGdwa2V5LnR4dIkCHAQTAQgABgWCVPgo4gAKCRCdYE0v
|
||||||
|
MQcWo5klD/9Qv79l+KSZUDwMMhtt3xt4iAnS5FmBDhpW7FE2JaHFBuXko9BqLO2v
|
||||||
|
CvcRKDJ/ceONTIaGDn9Q/45u+WgKpzAKmFKsE3EeISnfWMuKftB9Yum/M86O0wk0
|
||||||
|
R6rfJ5er5ikfDZVn6eh35cPvzli0sGqrVKKk1JkSJ9FiewuGpA6PpwG3QXZEsGbl
|
||||||
|
p8tt3gqN6VPtbvbb2ViUYmShATCuZVgBKIx94EMGccnVeCnU/R2bWBIRFVLNXapb
|
||||||
|
rDT+mw4erhyerhdZKf8hFbdRh8cgMNkkbXbmR5ihokBsGfML447h5XYqF5jl2SZp
|
||||||
|
5PRGaDOkTImrk9EM2mehe9Ji4L0oaI1NsKJDeWImdQmyVvMPC9PJN9rDFRtrh1eG
|
||||||
|
lxITyaNdr/3+8wkRHnJbTPORJr5+MghlwvQ9CqVN4MHMUxID1S31j6bq90bB6Uh7
|
||||||
|
Jvfx1a+wMLU1RRHanJ1OxG0UzWjuNS0aii9CUuHn/3H9gOHy+qnOKncvlJK83hF1
|
||||||
|
c3g/4RmivjF+LQ9VsegNm6zHI98oQWUKdfqT2oRpamVpwtuXwzya2m/77e9hVbqE
|
||||||
|
Desr/pd1+0UMmluaP7NNe+sBMT+W89wkeKzCK4wvH0E+gSK/UwZdPITpPEHkfsvV
|
||||||
|
4JBZ9tlieQRaK5eV8PKh+8N+5+IZOwEBDM3d4+6FhQ3OdQH7WzIPgQ==
|
||||||
|
=AYKY
|
||||||
|
-----END PGP PRIVATE KEY BLOCK-----
|
@ -0,0 +1,262 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse>
|
||||||
|
*
|
||||||
|
* 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.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.support.test.rule.ActivityTestRule;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
import android.test.suitebuilder.annotation.LargeTest;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
|
||||||
|
import org.hamcrest.CoreMatchers;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||||
|
import org.sufficientlysecure.keychain.ui.MainActivity;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||||
|
|
||||||
|
import static android.support.test.espresso.Espresso.onData;
|
||||||
|
import static android.support.test.espresso.Espresso.onView;
|
||||||
|
import static android.support.test.espresso.Espresso.pressBack;
|
||||||
|
import static android.support.test.espresso.action.ViewActions.click;
|
||||||
|
import static android.support.test.espresso.action.ViewActions.typeText;
|
||||||
|
import static android.support.test.espresso.assertion.ViewAssertions.matches;
|
||||||
|
import static android.support.test.espresso.contrib.DrawerActions.openDrawer;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA;
|
||||||
|
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.CoreMatchers.allOf;
|
||||||
|
import static org.hamcrest.CoreMatchers.not;
|
||||||
|
import static org.sufficientlysecure.keychain.TestHelpers.checkSnackbar;
|
||||||
|
import static org.sufficientlysecure.keychain.TestHelpers.importKeysFromResource;
|
||||||
|
import static org.sufficientlysecure.keychain.TestHelpers.randomString;
|
||||||
|
import static org.sufficientlysecure.keychain.actions.CustomActions.tokenEncryptViewAddToken;
|
||||||
|
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyItemId;
|
||||||
|
import static org.sufficientlysecure.keychain.matcher.DrawableMatcher.withDrawable;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@LargeTest
|
||||||
|
public class AsymmetricOperationTests {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final ActivityTestRule<MainActivity> mActivity
|
||||||
|
= new ActivityTestRule<MainActivity>(MainActivity.class) {
|
||||||
|
@Override
|
||||||
|
protected Intent getActivityIntent() {
|
||||||
|
Intent intent = super.getActivityIntent();
|
||||||
|
intent.putExtra(MainActivity.EXTRA_SKIP_FIRST_TIME, true);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
Activity activity = mActivity.getActivity();
|
||||||
|
|
||||||
|
// import these two, make sure they're there
|
||||||
|
importKeysFromResource(activity, "x.sec.asc");
|
||||||
|
|
||||||
|
// make sure no passphrases are cached
|
||||||
|
PassphraseCacheService.clearCachedPassphrases(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTextEncryptDecryptFromToken() throws Exception {
|
||||||
|
|
||||||
|
// navigate to 'encrypt text'
|
||||||
|
openDrawer(R.id.drawer_layout);
|
||||||
|
onView(withText(R.string.nav_encrypt_decrypt)).perform(click());
|
||||||
|
onView(withId(R.id.encrypt_text)).perform(click());
|
||||||
|
|
||||||
|
String cleartext = randomString(10, 30);
|
||||||
|
|
||||||
|
{ // encrypt
|
||||||
|
|
||||||
|
// the EncryptKeyCompletionView is tested individually
|
||||||
|
onView(withId(R.id.recipient_list)).perform(tokenEncryptViewAddToken(0x9D604D2F310716A3L));
|
||||||
|
|
||||||
|
onView(withId(R.id.encrypt_text_text)).perform(typeText(cleartext));
|
||||||
|
|
||||||
|
onView(withId(R.id.encrypt_copy)).perform(click());
|
||||||
|
}
|
||||||
|
|
||||||
|
// go to decrypt from clipboard view
|
||||||
|
pressBack();
|
||||||
|
onView(withId(R.id.decrypt_from_clipboard)).perform(click());
|
||||||
|
|
||||||
|
{ // decrypt
|
||||||
|
onView(withId(R.id.passphrase_passphrase)).perform(typeText("x"));
|
||||||
|
onView(withText(R.string.btn_unlock)).perform(click());
|
||||||
|
|
||||||
|
onView(withId(R.id.decrypt_text_plaintext)).check(matches(
|
||||||
|
withText(cleartext)));
|
||||||
|
|
||||||
|
onView(withId(R.id.result_encryption_text)).check(matches(
|
||||||
|
withText(R.string.decrypt_result_encrypted)));
|
||||||
|
onView(withId(R.id.result_signature_text)).check(matches(
|
||||||
|
withText(R.string.decrypt_result_no_signature)));
|
||||||
|
onView(withId(R.id.result_signature_layout)).check(matches(
|
||||||
|
not(isDisplayed())));
|
||||||
|
|
||||||
|
onView(withId(R.id.result_encryption_icon)).check(matches(
|
||||||
|
withDrawable(R.drawable.status_lock_closed_24dp)));
|
||||||
|
onView(withId(R.id.result_signature_icon)).check(matches(
|
||||||
|
withDrawable(R.drawable.status_signature_unknown_cutout_24dp)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTextEncryptDecryptFromKeyView() throws Exception {
|
||||||
|
|
||||||
|
String cleartext = randomString(10, 30);
|
||||||
|
|
||||||
|
{ // encrypt
|
||||||
|
|
||||||
|
// navigate to edit key dialog
|
||||||
|
onData(withKeyItemId(0x9D604D2F310716A3L))
|
||||||
|
.inAdapterView(allOf(isAssignableFrom(AdapterView.class),
|
||||||
|
isDescendantOfA(withId(R.id.key_list_list))))
|
||||||
|
.perform(click());
|
||||||
|
onView(withId(R.id.view_key_action_encrypt_text)).perform(click());
|
||||||
|
|
||||||
|
onView(withId(R.id.encrypt_text_text)).perform(typeText(cleartext));
|
||||||
|
|
||||||
|
onView(withId(R.id.encrypt_copy)).perform(click());
|
||||||
|
}
|
||||||
|
|
||||||
|
// go to decrypt from clipboard view
|
||||||
|
pressBack();
|
||||||
|
pressBack();
|
||||||
|
|
||||||
|
openDrawer(R.id.drawer_layout);
|
||||||
|
onView(withText(R.string.nav_encrypt_decrypt)).perform(click());
|
||||||
|
onView(withId(R.id.decrypt_from_clipboard)).perform(click());
|
||||||
|
|
||||||
|
{ // decrypt
|
||||||
|
|
||||||
|
onView(withId(R.id.passphrase_passphrase)).perform(typeText("x"));
|
||||||
|
onView(withText(R.string.btn_unlock)).perform(click());
|
||||||
|
|
||||||
|
onView(withId(R.id.decrypt_text_plaintext)).check(matches(
|
||||||
|
withText(cleartext)));
|
||||||
|
|
||||||
|
onView(withId(R.id.result_encryption_text)).check(matches(
|
||||||
|
withText(R.string.decrypt_result_encrypted)));
|
||||||
|
onView(withId(R.id.result_signature_text)).check(matches(
|
||||||
|
withText(R.string.decrypt_result_no_signature)));
|
||||||
|
onView(withId(R.id.result_signature_layout)).check(matches(
|
||||||
|
not(isDisplayed())));
|
||||||
|
|
||||||
|
onView(withId(R.id.result_encryption_icon)).check(matches(
|
||||||
|
withDrawable(R.drawable.status_lock_closed_24dp)));
|
||||||
|
onView(withId(R.id.result_signature_icon)).check(matches(
|
||||||
|
withDrawable(R.drawable.status_signature_unknown_cutout_24dp)));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pressBack();
|
||||||
|
onView(withId(R.id.decrypt_from_clipboard)).perform(click());
|
||||||
|
|
||||||
|
{ // decrypt again, passphrase should be cached
|
||||||
|
|
||||||
|
onView(withId(R.id.decrypt_text_plaintext)).check(matches(
|
||||||
|
withText(cleartext)));
|
||||||
|
|
||||||
|
onView(withId(R.id.result_encryption_text)).check(matches(
|
||||||
|
withText(R.string.decrypt_result_encrypted)));
|
||||||
|
onView(withId(R.id.result_signature_text)).check(matches(
|
||||||
|
withText(R.string.decrypt_result_no_signature)));
|
||||||
|
onView(withId(R.id.result_signature_layout)).check(matches(
|
||||||
|
not(isDisplayed())));
|
||||||
|
|
||||||
|
onView(withId(R.id.result_encryption_icon)).check(matches(
|
||||||
|
withDrawable(R.drawable.status_lock_closed_24dp)));
|
||||||
|
onView(withId(R.id.result_signature_icon)).check(matches(
|
||||||
|
withDrawable(R.drawable.status_signature_unknown_cutout_24dp)));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSignVerify() throws Exception {
|
||||||
|
|
||||||
|
String cleartext = randomString(10, 30);
|
||||||
|
|
||||||
|
// navigate to 'encrypt text'
|
||||||
|
openDrawer(R.id.drawer_layout);
|
||||||
|
onView(withText(R.string.nav_encrypt_decrypt)).perform(click());
|
||||||
|
onView(withId(R.id.encrypt_text)).perform(click());
|
||||||
|
|
||||||
|
{ // sign
|
||||||
|
|
||||||
|
onView(withId(R.id.encrypt_copy)).perform(click());
|
||||||
|
checkSnackbar(Style.ERROR, R.string.error_empty_text);
|
||||||
|
|
||||||
|
// navigate to edit key dialog
|
||||||
|
onView(withId(R.id.sign)).perform(click());
|
||||||
|
onData(withKeyItemId(0x9D604D2F310716A3L))
|
||||||
|
.inAdapterView(isAssignableFrom(AdapterView.class))
|
||||||
|
.perform(click());
|
||||||
|
|
||||||
|
onView(withId(R.id.encrypt_text_text)).perform(typeText(cleartext));
|
||||||
|
|
||||||
|
onView(withId(R.id.encrypt_copy)).perform(click());
|
||||||
|
|
||||||
|
onView(withId(R.id.passphrase_passphrase)).perform(typeText("x"));
|
||||||
|
onView(withText(R.string.btn_unlock)).perform(click());
|
||||||
|
|
||||||
|
checkSnackbar(Style.OK, R.string.msg_se_success);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// go to decrypt from clipboard view
|
||||||
|
pressBack();
|
||||||
|
|
||||||
|
onView(withId(R.id.decrypt_from_clipboard)).perform(click());
|
||||||
|
|
||||||
|
{ // decrypt
|
||||||
|
|
||||||
|
onView(withId(R.id.decrypt_text_plaintext)).check(matches(
|
||||||
|
// startsWith because there may be extra newlines
|
||||||
|
withText(CoreMatchers.startsWith(cleartext))));
|
||||||
|
|
||||||
|
onView(withId(R.id.result_encryption_text)).check(matches(
|
||||||
|
withText(R.string.decrypt_result_not_encrypted)));
|
||||||
|
onView(withId(R.id.result_signature_text)).check(matches(
|
||||||
|
withText(R.string.decrypt_result_signature_secret)));
|
||||||
|
onView(withId(R.id.result_signature_layout)).check(matches(
|
||||||
|
isDisplayed()));
|
||||||
|
|
||||||
|
onView(withId(R.id.result_encryption_icon)).check(matches(
|
||||||
|
withDrawable(R.drawable.status_lock_open_24dp)));
|
||||||
|
onView(withId(R.id.result_signature_icon)).check(matches(
|
||||||
|
withDrawable(R.drawable.status_signature_verified_cutout_24dp)));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -17,12 +17,14 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain;
|
package org.sufficientlysecure.keychain;
|
||||||
|
|
||||||
import android.support.test.rule.ActivityTestRule;
|
import android.support.test.InstrumentationRegistry;
|
||||||
import android.support.test.runner.AndroidJUnit4;
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
import android.test.ActivityInstrumentationTestCase2;
|
||||||
|
import android.test.suitebuilder.annotation.LargeTest;
|
||||||
import android.text.method.HideReturnsTransformationMethod;
|
import android.text.method.HideReturnsTransformationMethod;
|
||||||
import android.text.method.PasswordTransformationMethod;
|
import android.text.method.PasswordTransformationMethod;
|
||||||
|
|
||||||
import org.junit.Rule;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity;
|
import org.sufficientlysecure.keychain.ui.CreateKeyActivity;
|
||||||
@ -44,15 +46,24 @@ import static org.sufficientlysecure.keychain.matcher.EditTextMatchers.withError
|
|||||||
import static org.sufficientlysecure.keychain.matcher.EditTextMatchers.withTransformationMethod;
|
import static org.sufficientlysecure.keychain.matcher.EditTextMatchers.withTransformationMethod;
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class CreateKeyActivityTest {
|
@LargeTest
|
||||||
|
public class CreateKeyActivityTest extends ActivityInstrumentationTestCase2<CreateKeyActivity> {
|
||||||
|
|
||||||
public static final String SAMPLE_NAME = "Sample Name";
|
public static final String SAMPLE_NAME = "Sample Name";
|
||||||
public static final String SAMPLE_EMAIL = "sample_email@gmail.com";
|
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_ADDITIONAL_EMAIL = "sample_additional_email@gmail.com";
|
||||||
public static final String SAMPLE_PASSWORD = "sample_password";
|
public static final String SAMPLE_PASSWORD = "sample_password";
|
||||||
|
|
||||||
@Rule
|
public CreateKeyActivityTest() {
|
||||||
public ActivityTestRule<CreateKeyActivity> mActivityRule = new ActivityTestRule<>(CreateKeyActivity.class);
|
super(CreateKeyActivity.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
|
injectInstrumentation(InstrumentationRegistry.getInstrumentation());
|
||||||
|
getActivity();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateMyKey() {
|
public void testCreateMyKey() {
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse>
|
||||||
|
*
|
||||||
|
* 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.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.support.test.rule.ActivityTestRule;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
import android.test.suitebuilder.annotation.LargeTest;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
|
||||||
|
import org.junit.FixMethodOrder;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.MethodSorters;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||||
|
import org.sufficientlysecure.keychain.ui.MainActivity;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||||
|
|
||||||
|
import static android.support.test.espresso.Espresso.onData;
|
||||||
|
import static android.support.test.espresso.Espresso.onView;
|
||||||
|
import static android.support.test.espresso.action.ViewActions.click;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.withId;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.withText;
|
||||||
|
import static org.hamcrest.CoreMatchers.allOf;
|
||||||
|
import static org.sufficientlysecure.keychain.TestHelpers.checkSnackbar;
|
||||||
|
import static org.sufficientlysecure.keychain.TestHelpers.importKeysFromResource;
|
||||||
|
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyItemId;
|
||||||
|
|
||||||
|
|
||||||
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@LargeTest
|
||||||
|
public class EditKeyTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final ActivityTestRule<MainActivity> mActivity
|
||||||
|
= new ActivityTestRule<MainActivity>(MainActivity.class) {
|
||||||
|
@Override
|
||||||
|
protected Intent getActivityIntent() {
|
||||||
|
Intent intent = super.getActivityIntent();
|
||||||
|
intent.putExtra(MainActivity.EXTRA_SKIP_FIRST_TIME, true);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test01Edit() throws Exception {
|
||||||
|
Activity activity = mActivity.getActivity();
|
||||||
|
|
||||||
|
new KeychainDatabase(activity).clearDatabase();
|
||||||
|
|
||||||
|
// import key for testing, get a stable initial state
|
||||||
|
importKeysFromResource(activity, "x.sec.asc");
|
||||||
|
|
||||||
|
// navigate to edit key dialog
|
||||||
|
onData(withKeyItemId(0x9D604D2F310716A3L))
|
||||||
|
.inAdapterView(allOf(isAssignableFrom(AdapterView.class),
|
||||||
|
isDescendantOfA(withId(R.id.key_list_list))))
|
||||||
|
.perform(click());
|
||||||
|
onView(withId(R.id.menu_key_view_edit)).perform(click());
|
||||||
|
|
||||||
|
// no-op should yield snackbar
|
||||||
|
onView(withText(R.string.btn_save)).perform(click());
|
||||||
|
checkSnackbar(Style.ERROR, R.string.msg_mf_error_noop);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,132 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse>
|
||||||
|
*
|
||||||
|
* 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.content.Intent;
|
||||||
|
import android.support.test.espresso.matcher.ViewMatchers;
|
||||||
|
import android.support.test.rule.ActivityTestRule;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
import android.test.suitebuilder.annotation.LargeTest;
|
||||||
|
|
||||||
|
import org.junit.FixMethodOrder;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.MethodSorters;
|
||||||
|
import org.sufficientlysecure.keychain.ui.MainActivity;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||||
|
|
||||||
|
import static android.support.test.InstrumentationRegistry.getInstrumentation;
|
||||||
|
import static android.support.test.espresso.Espresso.onView;
|
||||||
|
import static android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu;
|
||||||
|
import static android.support.test.espresso.Espresso.pressBack;
|
||||||
|
import static android.support.test.espresso.action.ViewActions.click;
|
||||||
|
import static android.support.test.espresso.action.ViewActions.typeText;
|
||||||
|
import static android.support.test.espresso.assertion.ViewAssertions.matches;
|
||||||
|
import static android.support.test.espresso.contrib.DrawerActions.openDrawer;
|
||||||
|
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.CoreMatchers.not;
|
||||||
|
import static org.sufficientlysecure.keychain.TestHelpers.checkSnackbar;
|
||||||
|
import static org.sufficientlysecure.keychain.TestHelpers.randomString;
|
||||||
|
import static org.sufficientlysecure.keychain.matcher.DrawableMatcher.withDrawable;
|
||||||
|
|
||||||
|
|
||||||
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@LargeTest
|
||||||
|
public class EncryptDecryptSymmetricTests {
|
||||||
|
|
||||||
|
public static final String PASSPHRASE = randomString(5, 20);
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final ActivityTestRule<MainActivity> mActivity
|
||||||
|
= new ActivityTestRule<MainActivity>(MainActivity.class) {
|
||||||
|
@Override
|
||||||
|
protected Intent getActivityIntent() {
|
||||||
|
Intent intent = super.getActivityIntent();
|
||||||
|
intent.putExtra(MainActivity.EXTRA_SKIP_FIRST_TIME, true);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSymmetricTextEncryptDecrypt() throws Exception {
|
||||||
|
|
||||||
|
MainActivity activity = mActivity.getActivity();
|
||||||
|
|
||||||
|
String text = randomString(10, 30);
|
||||||
|
|
||||||
|
// navigate to encrypt/decrypt
|
||||||
|
openDrawer(R.id.drawer_layout);
|
||||||
|
onView(ViewMatchers.withText(R.string.nav_encrypt_decrypt)).perform(click());
|
||||||
|
onView(withId(R.id.encrypt_text)).perform(click());
|
||||||
|
|
||||||
|
{
|
||||||
|
onView(withId(R.id.encrypt_text_text)).perform(typeText(text));
|
||||||
|
|
||||||
|
openActionBarOverflowOrOptionsMenu(getInstrumentation().getTargetContext());
|
||||||
|
onView(withText(R.string.label_symmetric)).perform(click());
|
||||||
|
|
||||||
|
onView(withId(R.id.passphrase)).perform(typeText(PASSPHRASE));
|
||||||
|
|
||||||
|
onView(withId(R.id.encrypt_copy)).perform(click());
|
||||||
|
|
||||||
|
checkSnackbar(Style.ERROR, R.string.passphrases_do_not_match);
|
||||||
|
|
||||||
|
onView(withId(R.id.passphraseAgain)).perform(typeText(PASSPHRASE));
|
||||||
|
|
||||||
|
onView(withId(R.id.encrypt_text_text)).check(matches(withText(text)));
|
||||||
|
|
||||||
|
onView(withId(R.id.encrypt_copy)).perform(click());
|
||||||
|
|
||||||
|
checkSnackbar(Style.OK, R.string.msg_se_success);
|
||||||
|
}
|
||||||
|
|
||||||
|
// go to decrypt from clipboard view
|
||||||
|
pressBack();
|
||||||
|
onView(withId(R.id.decrypt_from_clipboard)).perform(click());
|
||||||
|
|
||||||
|
{
|
||||||
|
onView(withId(R.id.passphrase_passphrase)).perform(typeText(PASSPHRASE));
|
||||||
|
onView(withText(R.string.btn_unlock)).perform(click());
|
||||||
|
|
||||||
|
onView(withId(R.id.decrypt_text_plaintext)).check(matches(
|
||||||
|
withText(text)));
|
||||||
|
|
||||||
|
// TODO write generic status verifier
|
||||||
|
|
||||||
|
onView(withId(R.id.result_encryption_text)).check(matches(
|
||||||
|
withText(R.string.decrypt_result_encrypted)));
|
||||||
|
onView(withId(R.id.result_signature_text)).check(matches(
|
||||||
|
withText(R.string.decrypt_result_no_signature)));
|
||||||
|
onView(withId(R.id.result_signature_layout)).check(matches(
|
||||||
|
not(isDisplayed())));
|
||||||
|
|
||||||
|
onView(withId(R.id.result_encryption_icon)).check(matches(
|
||||||
|
withDrawable(R.drawable.status_lock_closed_24dp)));
|
||||||
|
onView(withId(R.id.result_signature_icon)).check(matches(
|
||||||
|
withDrawable(R.drawable.status_signature_unknown_cutout_24dp)));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse>
|
||||||
|
*
|
||||||
|
* 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.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.support.test.espresso.action.ViewActions;
|
||||||
|
import android.support.test.espresso.matcher.RootMatchers;
|
||||||
|
import android.support.test.rule.ActivityTestRule;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
import android.test.suitebuilder.annotation.LargeTest;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.sufficientlysecure.keychain.ui.EncryptTextActivity;
|
||||||
|
|
||||||
|
import static android.support.test.espresso.Espresso.onData;
|
||||||
|
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.typeText;
|
||||||
|
import static android.support.test.espresso.assertion.ViewAssertions.matches;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.withId;
|
||||||
|
import static org.hamcrest.CoreMatchers.allOf;
|
||||||
|
import static org.sufficientlysecure.keychain.TestHelpers.importKeysFromResource;
|
||||||
|
import static org.sufficientlysecure.keychain.actions.CustomActions.tokenEncryptViewAddToken;
|
||||||
|
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyItemId;
|
||||||
|
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyToken;
|
||||||
|
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@LargeTest
|
||||||
|
public class EncryptKeyCompletionViewTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final ActivityTestRule<EncryptTextActivity> mActivity
|
||||||
|
= new ActivityTestRule<>(EncryptTextActivity.class);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTextEncryptDecryptFromToken() throws Exception {
|
||||||
|
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.putExtra(EncryptTextActivity.EXTRA_ENCRYPTION_KEY_IDS, new long[] { 0x9D604D2F310716A3L });
|
||||||
|
Activity activity = mActivity.launchActivity(intent);
|
||||||
|
|
||||||
|
// import these two, make sure they're there
|
||||||
|
importKeysFromResource(activity, "x.sec.asc");
|
||||||
|
|
||||||
|
// check if the element passed in from intent
|
||||||
|
onView(withId(R.id.recipient_list)).check(matches(withKeyToken(0x9D604D2F310716A3L)));
|
||||||
|
onView(withId(R.id.recipient_list)).perform(ViewActions.pressKey(KeyEvent.KEYCODE_DEL));
|
||||||
|
|
||||||
|
// type X, select from list, check if it's there
|
||||||
|
onView(withId(R.id.recipient_list)).perform(typeText("x"));
|
||||||
|
onData(withKeyItemId(0x9D604D2F310716A3L)).inRoot(RootMatchers.isPlatformPopup())
|
||||||
|
.inAdapterView(allOf(isAssignableFrom(AdapterView.class),
|
||||||
|
hasDescendant(withId(R.id.key_list_item_name)))).perform(click());
|
||||||
|
onView(withId(R.id.recipient_list)).check(matches(withKeyToken(0x9D604D2F310716A3L)));
|
||||||
|
onView(withId(R.id.recipient_list)).perform(ViewActions.pressKey(KeyEvent.KEYCODE_DEL));
|
||||||
|
onView(withId(R.id.recipient_list)).perform(ViewActions.pressKey(KeyEvent.KEYCODE_DEL));
|
||||||
|
|
||||||
|
// add directly, check if it's there
|
||||||
|
onView(withId(R.id.recipient_list)).perform(tokenEncryptViewAddToken(0x9D604D2F310716A3L));
|
||||||
|
onView(withId(R.id.recipient_list)).check(matches(withKeyToken(0x9D604D2F310716A3L)));
|
||||||
|
onView(withId(R.id.recipient_list)).perform(ViewActions.pressKey(KeyEvent.KEYCODE_DEL));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package org.sufficientlysecure.keychain;
|
||||||
|
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.test.runner.AndroidJUnitRunner;
|
||||||
|
|
||||||
|
|
||||||
|
public class JacocoWorkaroundJUnitRunner extends AndroidJUnitRunner {
|
||||||
|
static {
|
||||||
|
System.setProperty("jacoco-agent.destfile", "/data/data/"
|
||||||
|
+ BuildConfig.APPLICATION_ID + "/coverage.ec");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void finish(int resultCode, Bundle results) {
|
||||||
|
try {
|
||||||
|
Class rt = Class.forName("org.jacoco.agent.rt.RT");
|
||||||
|
Method getAgent = rt.getMethod("getAgent");
|
||||||
|
Method dump = getAgent.getReturnType().getMethod("dump", boolean.class);
|
||||||
|
Object agent = getAgent.invoke(null);
|
||||||
|
dump.invoke(agent, false);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
super.finish(resultCode, results);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse>
|
||||||
|
*
|
||||||
|
* 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 java.util.Random;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.annotation.StringRes;
|
||||||
|
|
||||||
|
import org.hamcrest.CoreMatchers;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow;
|
||||||
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||||
|
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||||
|
|
||||||
|
import static android.support.test.InstrumentationRegistry.getInstrumentation;
|
||||||
|
import static android.support.test.espresso.Espresso.onView;
|
||||||
|
import static android.support.test.espresso.assertion.ViewAssertions.matches;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.withClassName;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.withText;
|
||||||
|
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withSnackbarLineColor;
|
||||||
|
|
||||||
|
|
||||||
|
public class TestHelpers {
|
||||||
|
|
||||||
|
|
||||||
|
public static void checkSnackbar(Style style, @StringRes Integer text) {
|
||||||
|
|
||||||
|
onView(withClassName(CoreMatchers.endsWith("Snackbar")))
|
||||||
|
.check(matches(withSnackbarLineColor(style.mLineColor)));
|
||||||
|
|
||||||
|
if (text != null) {
|
||||||
|
onView(withClassName(CoreMatchers.endsWith("Snackbar")))
|
||||||
|
.check(matches(hasDescendant(withText(text))));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void importKeysFromResource(Context context, String name) throws Exception {
|
||||||
|
IteratorWithIOThrow<UncachedKeyRing> stream = UncachedKeyRing.fromStream(
|
||||||
|
getInstrumentation().getContext().getAssets().open(name));
|
||||||
|
|
||||||
|
ProviderHelper helper = new ProviderHelper(context);
|
||||||
|
while(stream.hasNext()) {
|
||||||
|
UncachedKeyRing ring = stream.next();
|
||||||
|
if (ring.isSecret()) {
|
||||||
|
helper.saveSecretKeyRing(ring, new ProgressScaler());
|
||||||
|
} else {
|
||||||
|
helper.savePublicKeyRing(ring, new ProgressScaler());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String randomString(int min, int max) {
|
||||||
|
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789!@#$%^&*()-_=";
|
||||||
|
Random r = new Random();
|
||||||
|
StringBuilder passbuilder = new StringBuilder();
|
||||||
|
// 5% chance for an empty string
|
||||||
|
for(int i = 0, j = r.nextInt(max)+min; i < j; i++) {
|
||||||
|
passbuilder.append(chars.charAt(r.nextInt(chars.length())));
|
||||||
|
}
|
||||||
|
return passbuilder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse>
|
||||||
|
*
|
||||||
|
* 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.actions;
|
||||||
|
|
||||||
|
|
||||||
|
import android.support.test.espresso.UiController;
|
||||||
|
import android.support.test.espresso.ViewAction;
|
||||||
|
import android.support.test.espresso.matcher.ViewMatchers;
|
||||||
|
import android.support.v4.view.GravityCompat;
|
||||||
|
import android.support.v4.widget.DrawerLayout;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.tokenautocomplete.TokenCompleteTextView;
|
||||||
|
import org.hamcrest.Matcher;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||||
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
|
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
|
||||||
|
|
||||||
|
import static android.support.test.InstrumentationRegistry.getTargetContext;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class CustomActions {
|
||||||
|
|
||||||
|
public static ViewAction tokenEncryptViewAddToken(long keyId) throws Exception {
|
||||||
|
CanonicalizedPublicKeyRing ring =
|
||||||
|
new ProviderHelper(getTargetContext()).getCanonicalizedPublicKeyRing(keyId);
|
||||||
|
final Object item = new KeyAdapter.KeyItem(ring);
|
||||||
|
|
||||||
|
return new ViewAction() {
|
||||||
|
@Override
|
||||||
|
public Matcher<View> getConstraints() {
|
||||||
|
return ViewMatchers.isAssignableFrom(TokenCompleteTextView.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return "add completion token";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void perform(UiController uiController, View view) {
|
||||||
|
((TokenCompleteTextView) view).addObject(item);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ViewAction tokenViewAddToken(final Object item) {
|
||||||
|
return new ViewAction() {
|
||||||
|
@Override
|
||||||
|
public Matcher<View> getConstraints() {
|
||||||
|
return ViewMatchers.isAssignableFrom(TokenCompleteTextView.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return "add completion token";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void perform(UiController uiController, View view) {
|
||||||
|
((TokenCompleteTextView) view).addObject(item);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse>
|
||||||
|
*
|
||||||
|
* 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.support.annotation.ColorRes;
|
||||||
|
import android.support.test.espresso.matcher.BoundedMatcher;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.nispok.snackbar.Snackbar;
|
||||||
|
import org.hamcrest.Description;
|
||||||
|
import org.hamcrest.Matcher;
|
||||||
|
import org.sufficientlysecure.keychain.EncryptKeyCompletionViewTest;
|
||||||
|
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem;
|
||||||
|
import org.sufficientlysecure.keychain.ui.widget.EncryptKeyCompletionView;
|
||||||
|
|
||||||
|
import static android.support.test.internal.util.Checks.checkNotNull;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class CustomMatchers {
|
||||||
|
|
||||||
|
public static Matcher<View> withSnackbarLineColor(@ColorRes final int colorRes) {
|
||||||
|
return new BoundedMatcher<View, Snackbar>(Snackbar.class) {
|
||||||
|
public void describeTo(Description description) {
|
||||||
|
description.appendText("with color resource id: " + colorRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matchesSafely(Snackbar snackbar) {
|
||||||
|
return snackbar.getResources().getColor(colorRes) == snackbar.getLineColor();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Matcher<Object> withKeyItemId(final long keyId) {
|
||||||
|
return new BoundedMatcher<Object, KeyItem>(KeyItem.class) {
|
||||||
|
@Override
|
||||||
|
public boolean matchesSafely(KeyItem item) {
|
||||||
|
return item.mKeyId == keyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void describeTo(Description description) {
|
||||||
|
description.appendText("with key id: " + keyId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Matcher<View> withKeyToken(@ColorRes final long keyId) {
|
||||||
|
return new BoundedMatcher<View, EncryptKeyCompletionView>(EncryptKeyCompletionView.class) {
|
||||||
|
public void describeTo(Description description) {
|
||||||
|
description.appendText("with key id token: " + keyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matchesSafely(EncryptKeyCompletionView tokenView) {
|
||||||
|
for (Object object : tokenView.getObjects()) {
|
||||||
|
if (object instanceof KeyItem && ((KeyItem) object).mKeyId == keyId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Xavi Rigau <xrigau@gmail.com>
|
||||||
|
* Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse>
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*
|
||||||
|
* From the droidcon anroid espresso repository.
|
||||||
|
* https://github.com/xrigau/droidcon-android-espresso/
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.sufficientlysecure.keychain.matcher;
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.hamcrest.Description;
|
||||||
|
import org.hamcrest.TypeSafeMatcher;
|
||||||
|
|
||||||
|
|
||||||
|
public class DrawableMatcher extends TypeSafeMatcher<View> {
|
||||||
|
|
||||||
|
private final int mResourceId;
|
||||||
|
private final boolean mIgnoreFilters;
|
||||||
|
|
||||||
|
public DrawableMatcher(int resourceId, boolean ignoreFilters) {
|
||||||
|
super(View.class);
|
||||||
|
mResourceId = resourceId;
|
||||||
|
mIgnoreFilters = ignoreFilters;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resourceName = null;
|
||||||
|
private Drawable expectedDrawable = null;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matchesSafely(View target) {
|
||||||
|
if (expectedDrawable == null) {
|
||||||
|
loadDrawableFromResources(target.getResources());
|
||||||
|
}
|
||||||
|
if (invalidExpectedDrawable()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target instanceof ImageView) {
|
||||||
|
return hasImage((ImageView) target) || hasBackground(target);
|
||||||
|
}
|
||||||
|
if (target instanceof TextView) {
|
||||||
|
return hasCompoundDrawable((TextView) target) || hasBackground(target);
|
||||||
|
}
|
||||||
|
return hasBackground(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadDrawableFromResources(Resources resources) {
|
||||||
|
try {
|
||||||
|
expectedDrawable = resources.getDrawable(mResourceId);
|
||||||
|
resourceName = resources.getResourceEntryName(mResourceId);
|
||||||
|
} catch (Resources.NotFoundException ignored) {
|
||||||
|
// view could be from a context unaware of the resource id.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean invalidExpectedDrawable() {
|
||||||
|
return expectedDrawable == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasImage(ImageView target) {
|
||||||
|
return isSameDrawable(target.getDrawable());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasCompoundDrawable(TextView target) {
|
||||||
|
for (Drawable drawable : target.getCompoundDrawables()) {
|
||||||
|
if (isSameDrawable(drawable)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasBackground(View target) {
|
||||||
|
return isSameDrawable(target.getBackground());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSameDrawable(Drawable drawable) {
|
||||||
|
if (drawable == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// if those are both bitmap drawables, compare their bitmaps (ignores color filters, which is what we want!)
|
||||||
|
if (mIgnoreFilters && drawable instanceof BitmapDrawable && expectedDrawable instanceof BitmapDrawable) {
|
||||||
|
return ((BitmapDrawable) drawable).getBitmap().equals(((BitmapDrawable) expectedDrawable).getBitmap());
|
||||||
|
}
|
||||||
|
return expectedDrawable.getConstantState().equals(drawable.getConstantState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void describeTo(Description description) {
|
||||||
|
description.appendText("with drawable from resource id: ");
|
||||||
|
description.appendValue(mResourceId);
|
||||||
|
if (resourceName != null) {
|
||||||
|
description.appendText("[");
|
||||||
|
description.appendText(resourceName);
|
||||||
|
description.appendText("]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DrawableMatcher withDrawable(int resourceId, boolean ignoreFilters) {
|
||||||
|
return new DrawableMatcher(resourceId, ignoreFilters);
|
||||||
|
}
|
||||||
|
public static DrawableMatcher withDrawable(int resourceId) {
|
||||||
|
return new DrawableMatcher(resourceId, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
* Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse>
|
||||||
*
|
*
|
||||||
* 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
|
||||||
|
@ -74,6 +74,10 @@ public class ClipboardReflection {
|
|||||||
Method methodGetPrimaryClip = clipboard.getClass().getMethod("getPrimaryClip");
|
Method methodGetPrimaryClip = clipboard.getClass().getMethod("getPrimaryClip");
|
||||||
Object clipData = methodGetPrimaryClip.invoke(clipboard);
|
Object clipData = methodGetPrimaryClip.invoke(clipboard);
|
||||||
|
|
||||||
|
if (clipData == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// ClipData.Item clipDataItem = clipData.getItemAt(0);
|
// ClipData.Item clipDataItem = clipData.getItemAt(0);
|
||||||
Method methodGetItemAt = clipData.getClass().getMethod("getItemAt", int.class);
|
Method methodGetItemAt = clipData.getClass().getMethod("getItemAt", int.class);
|
||||||
Object clipDataItem = methodGetItemAt.invoke(clipData, 0);
|
Object clipDataItem = methodGetItemAt.invoke(clipData, 0);
|
||||||
|
@ -82,7 +82,7 @@ public class EditKeyOperation extends BaseOperation<SaveKeyringParcel> {
|
|||||||
CanonicalizedSecretKeyRing secRing =
|
CanonicalizedSecretKeyRing secRing =
|
||||||
mProviderHelper.getCanonicalizedSecretKeyRing(saveParcel.mMasterKeyId);
|
mProviderHelper.getCanonicalizedSecretKeyRing(saveParcel.mMasterKeyId);
|
||||||
|
|
||||||
modifyResult = keyOperations.modifySecretKeyRing(secRing, cryptoInput, saveParcel, log, 2);
|
modifyResult = keyOperations.modifySecretKeyRing(secRing, cryptoInput, saveParcel);
|
||||||
if (modifyResult.isPending()) {
|
if (modifyResult.isPending()) {
|
||||||
return modifyResult;
|
return modifyResult;
|
||||||
}
|
}
|
||||||
|
@ -854,7 +854,11 @@ public abstract class OperationResult implements Parcelable {
|
|||||||
if (mParcels.isEmpty()) {
|
if (mParcels.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return mParcels.get(mParcels.size() -1);
|
LogEntryParcel last = mParcels.get(mParcels.size() -1);
|
||||||
|
if (last instanceof SubLogEntryParcel) {
|
||||||
|
return ((SubLogEntryParcel) last).getSubResult().getLog().getLast();
|
||||||
|
}
|
||||||
|
return last;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -149,7 +149,7 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
|
|||||||
return verifySignedLiteralData(input, aIn, outputStream, 0);
|
return verifySignedLiteralData(input, aIn, outputStream, 0);
|
||||||
} else if (aIn.isClearText()) {
|
} else if (aIn.isClearText()) {
|
||||||
// a cleartext signature, verify it with the other method
|
// a cleartext signature, verify it with the other method
|
||||||
return verifyCleartextSignature(aIn, 0);
|
return verifyCleartextSignature(aIn, outputStream, 0);
|
||||||
} else {
|
} else {
|
||||||
// else: ascii armored encryption! go on...
|
// else: ascii armored encryption! go on...
|
||||||
return decryptVerify(input, cryptoInput, in, outputStream, 0);
|
return decryptVerify(input, cryptoInput, in, outputStream, 0);
|
||||||
@ -757,6 +757,12 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
|
|||||||
// TODO: slow annealing to fake a progress?
|
// TODO: slow annealing to fake a progress?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
metadata = new OpenPgpMetadata(
|
||||||
|
originalFilename,
|
||||||
|
mimeType,
|
||||||
|
literalData.getModificationTime().getTime(),
|
||||||
|
alreadyWritten);
|
||||||
|
|
||||||
if (signature != null) {
|
if (signature != null) {
|
||||||
updateProgress(R.string.progress_verifying_signature, 90, 100);
|
updateProgress(R.string.progress_verifying_signature, 90, 100);
|
||||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_CHECK, indent);
|
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_CHECK, indent);
|
||||||
@ -833,7 +839,7 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
|
|||||||
* pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java
|
* pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java
|
||||||
*/
|
*/
|
||||||
private DecryptVerifyResult verifyCleartextSignature(
|
private DecryptVerifyResult verifyCleartextSignature(
|
||||||
ArmoredInputStream aIn, int indent) throws IOException, PGPException {
|
ArmoredInputStream aIn, OutputStream outputStream, int indent) throws IOException, PGPException {
|
||||||
|
|
||||||
OperationLog log = new OperationLog();
|
OperationLog log = new OperationLog();
|
||||||
|
|
||||||
@ -863,8 +869,9 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
|
|||||||
out.close();
|
out.close();
|
||||||
|
|
||||||
byte[] clearText = out.toByteArray();
|
byte[] clearText = out.toByteArray();
|
||||||
if (out != null) {
|
if (outputStream != null) {
|
||||||
out.write(clearText);
|
outputStream.write(clearText);
|
||||||
|
outputStream.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateProgress(R.string.progress_processing_signature, 60, 100);
|
updateProgress(R.string.progress_processing_signature, 60, 100);
|
||||||
|
@ -365,14 +365,9 @@ public class PgpKeyOperation {
|
|||||||
public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR,
|
public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR,
|
||||||
CryptoInputParcel cryptoInput,
|
CryptoInputParcel cryptoInput,
|
||||||
SaveKeyringParcel saveParcel) {
|
SaveKeyringParcel saveParcel) {
|
||||||
return modifySecretKeyRing(wsKR, cryptoInput, saveParcel, new OperationLog(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR,
|
OperationLog log = new OperationLog();
|
||||||
CryptoInputParcel cryptoInput,
|
int indent = 0;
|
||||||
SaveKeyringParcel saveParcel,
|
|
||||||
OperationLog log,
|
|
||||||
int indent) {
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 1. Unlock private key
|
* 1. Unlock private key
|
||||||
|
@ -179,7 +179,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
|||||||
+ Tables.API_APPS + "(" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") ON DELETE CASCADE"
|
+ Tables.API_APPS + "(" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") ON DELETE CASCADE"
|
||||||
+ ")";
|
+ ")";
|
||||||
|
|
||||||
KeychainDatabase(Context context) {
|
public KeychainDatabase(Context context) {
|
||||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||||
mContext = context;
|
mContext = context;
|
||||||
|
|
||||||
|
@ -149,6 +149,14 @@ public class PassphraseCacheService extends Service {
|
|||||||
context.startService(intent);
|
context.startService(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void clearCachedPassphrases(Context context) {
|
||||||
|
Log.d(Constants.TAG, "PassphraseCacheService.clearCachedPassphrase()");
|
||||||
|
|
||||||
|
Intent intent = new Intent(context, PassphraseCacheService.class);
|
||||||
|
intent.setAction(ACTION_PASSPHRASE_CACHE_CLEAR);
|
||||||
|
|
||||||
|
context.startService(intent);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a cached passphrase from memory by sending an intent to the service. This method is
|
* Gets a cached passphrase from memory by sending an intent to the service. This method is
|
||||||
|
@ -153,7 +153,9 @@ public class DecryptTextFragment extends DecryptFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected PgpDecryptVerifyInputParcel createOperationInput() {
|
protected PgpDecryptVerifyInputParcel createOperationInput() {
|
||||||
return new PgpDecryptVerifyInputParcel(mCiphertext.getBytes());
|
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(mCiphertext.getBytes());
|
||||||
|
input.setAllowSymmetricDecryption(true);
|
||||||
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -75,7 +75,6 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment {
|
|||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.encrypt_asymmetric_fragment, container, false);
|
View view = inflater.inflate(R.layout.encrypt_asymmetric_fragment, container, false);
|
||||||
|
|
||||||
|
|
||||||
mSignKeySpinner = (KeySpinner) view.findViewById(R.id.sign);
|
mSignKeySpinner = (KeySpinner) view.findViewById(R.id.sign);
|
||||||
mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list);
|
mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list);
|
||||||
mEncryptKeyView.setThreshold(1); // Start working from first character
|
mEncryptKeyView.setThreshold(1); // Start working from first character
|
||||||
@ -168,7 +167,7 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getAsymmetricSigningKeyId() {
|
public long getAsymmetricSigningKeyId() {
|
||||||
return mSignKeySpinner.getSelectedItemId();
|
return mSignKeySpinner.getSelectedKeyId();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -240,7 +240,7 @@ public class EncryptTextFragment
|
|||||||
boolean gotEncryptionKeys = (encryptionKeyIds != null
|
boolean gotEncryptionKeys = (encryptionKeyIds != null
|
||||||
&& encryptionKeyIds.length > 0);
|
&& encryptionKeyIds.length > 0);
|
||||||
|
|
||||||
if (!gotEncryptionKeys && signingKeyId == 0L) {
|
if (!gotEncryptionKeys && signingKeyId == Constants.key.none) {
|
||||||
Notify.create(getActivity(), R.string.select_encryption_or_signature_key, Notify.Style.ERROR)
|
Notify.create(getActivity(), R.string.select_encryption_or_signature_key, Notify.Style.ERROR)
|
||||||
.show(this);
|
.show(this);
|
||||||
return null;
|
return null;
|
||||||
|
@ -51,6 +51,8 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
|
|||||||
private static final int ID_SETTINGS = 4;
|
private static final int ID_SETTINGS = 4;
|
||||||
private static final int ID_HELP = 5;
|
private static final int ID_HELP = 5;
|
||||||
|
|
||||||
|
public static final String EXTRA_SKIP_FIRST_TIME = "skip_first_time";
|
||||||
|
|
||||||
public Drawer.Result mDrawerResult;
|
public Drawer.Result mDrawerResult;
|
||||||
private Toolbar mToolbar;
|
private Toolbar mToolbar;
|
||||||
|
|
||||||
@ -114,7 +116,7 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
|
|||||||
|
|
||||||
// if this is the first time show first time activity
|
// if this is the first time show first time activity
|
||||||
Preferences prefs = Preferences.getPreferences(this);
|
Preferences prefs = Preferences.getPreferences(this);
|
||||||
if (prefs.isFirstTime()) {
|
if (!getIntent().getBooleanExtra(EXTRA_SKIP_FIRST_TIME, false) && prefs.isFirstTime()) {
|
||||||
Intent intent = new Intent(this, CreateKeyActivity.class);
|
Intent intent = new Intent(this, CreateKeyActivity.class);
|
||||||
intent.putExtra(CreateKeyActivity.EXTRA_FIRST_TIME, true);
|
intent.putExtra(CreateKeyActivity.EXTRA_FIRST_TIME, true);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
@ -43,14 +44,12 @@ 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.compatibility.DialogFragmentWorkaround;
|
|
||||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
||||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
|
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.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.CachedPublicKeyRing;
|
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
|
||||||
@ -75,8 +74,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
|||||||
|
|
||||||
// special extra for OpenPgpService
|
// special extra for OpenPgpService
|
||||||
public static final String EXTRA_SERVICE_INTENT = "data";
|
public static final String EXTRA_SERVICE_INTENT = "data";
|
||||||
|
private long mSubKeyId;
|
||||||
private static final int REQUEST_CODE_ENTER_PATTERN = 2;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
@ -93,14 +91,13 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
|||||||
|
|
||||||
// this activity itself has no content view (see manifest)
|
// this activity itself has no content view (see manifest)
|
||||||
|
|
||||||
long keyId;
|
|
||||||
if (getIntent().hasExtra(EXTRA_SUBKEY_ID)) {
|
if (getIntent().hasExtra(EXTRA_SUBKEY_ID)) {
|
||||||
keyId = getIntent().getLongExtra(EXTRA_SUBKEY_ID, 0);
|
mSubKeyId = getIntent().getLongExtra(EXTRA_SUBKEY_ID, 0);
|
||||||
} else {
|
} else {
|
||||||
RequiredInputParcel requiredInput = getIntent().getParcelableExtra(EXTRA_REQUIRED_INPUT);
|
RequiredInputParcel requiredInput = getIntent().getParcelableExtra(EXTRA_REQUIRED_INPUT);
|
||||||
switch (requiredInput.mType) {
|
switch (requiredInput.mType) {
|
||||||
case PASSPHRASE_SYMMETRIC: {
|
case PASSPHRASE_SYMMETRIC: {
|
||||||
keyId = Constants.key.symmetric;
|
mSubKeyId = Constants.key.symmetric;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PASSPHRASE: {
|
case PASSPHRASE: {
|
||||||
@ -127,7 +124,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
keyId = requiredInput.getSubKeyId();
|
mSubKeyId = requiredInput.getSubKeyId();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
@ -136,62 +133,35 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Intent serviceIntent = getIntent().getParcelableExtra(EXTRA_SERVICE_INTENT);
|
|
||||||
|
|
||||||
show(this, keyId, serviceIntent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
protected void onResume() {
|
||||||
switch (requestCode) {
|
super.onResume();
|
||||||
case REQUEST_CODE_ENTER_PATTERN: {
|
|
||||||
/*
|
|
||||||
* NOTE that there are 4 possible result codes!!!
|
|
||||||
*/
|
|
||||||
switch (resultCode) {
|
|
||||||
case RESULT_OK:
|
|
||||||
// The user passed
|
|
||||||
break;
|
|
||||||
case RESULT_CANCELED:
|
|
||||||
// The user cancelled the task
|
|
||||||
break;
|
|
||||||
// case LockPatternActivity.RESULT_FAILED:
|
|
||||||
// // The user failed to enter the pattern
|
|
||||||
// break;
|
|
||||||
// case LockPatternActivity.RESULT_FORGOT_PATTERN:
|
|
||||||
// // The user forgot the pattern and invoked your recovery Activity.
|
|
||||||
// break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/* Show passphrase dialog to cache a new passphrase the user enters for using it later for
|
||||||
* In any case, there's always a key EXTRA_RETRY_COUNT, which holds
|
|
||||||
* the number of tries that the user did.
|
|
||||||
*/
|
|
||||||
// int retryCount = data.getIntExtra(
|
|
||||||
// LockPatternActivity.EXTRA_RETRY_COUNT, 0);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows passphrase dialog to cache a new passphrase the user enters for using it later for
|
|
||||||
* encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks
|
* encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks
|
||||||
* for a symmetric passphrase
|
* for a symmetric passphrase
|
||||||
*/
|
*/
|
||||||
public static void show(final FragmentActivity context, final long keyId, final Intent serviceIntent) {
|
|
||||||
DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {
|
Intent serviceIntent = getIntent().getParcelableExtra(EXTRA_SERVICE_INTENT);
|
||||||
public void run() {
|
|
||||||
// do NOT check if the key even needs a passphrase. that's not our job here.
|
|
||||||
PassphraseDialogFragment frag = new PassphraseDialogFragment();
|
PassphraseDialogFragment frag = new PassphraseDialogFragment();
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putLong(EXTRA_SUBKEY_ID, keyId);
|
args.putLong(EXTRA_SUBKEY_ID, mSubKeyId);
|
||||||
args.putParcelable(EXTRA_SERVICE_INTENT, serviceIntent);
|
args.putParcelable(EXTRA_SERVICE_INTENT, serviceIntent);
|
||||||
frag.setArguments(args);
|
frag.setArguments(args);
|
||||||
frag.show(context.getSupportFragmentManager(), "passphraseDialog");
|
frag.show(getSupportFragmentManager(), "passphraseDialog");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
|
||||||
|
DialogFragment dialog = (DialogFragment) getSupportFragmentManager().findFragmentByTag("passphraseDialog");
|
||||||
|
if (dialog != null) {
|
||||||
|
dialog.dismiss();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class PassphraseDialogFragment extends DialogFragment implements TextView.OnEditorActionListener {
|
public static class PassphraseDialogFragment extends DialogFragment implements TextView.OnEditorActionListener {
|
||||||
@ -205,9 +175,6 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
|||||||
|
|
||||||
private Intent mServiceIntent;
|
private Intent mServiceIntent;
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates dialog
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
final Activity activity = getActivity();
|
final Activity activity = getActivity();
|
||||||
@ -268,12 +235,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
|||||||
userId = null;
|
userId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get key type for message */
|
keyType = mSecretRing.getSecretKey(mSubKeyId).getSecretKeyType();
|
||||||
// find a master key id for our key
|
|
||||||
long masterKeyId = new ProviderHelper(activity).getMasterKeyId(mSubKeyId);
|
|
||||||
CachedPublicKeyRing keyRing = new ProviderHelper(activity).getCachedPublicKeyRing(masterKeyId);
|
|
||||||
// get the type of key (from the database)
|
|
||||||
keyType = keyRing.getSecretKeyType(mSubKeyId);
|
|
||||||
switch (keyType) {
|
switch (keyType) {
|
||||||
case PASSPHRASE:
|
case PASSPHRASE:
|
||||||
message = getString(R.string.passphrase_for, userId);
|
message = getString(R.string.passphrase_for, userId);
|
||||||
@ -468,20 +430,16 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
|||||||
|
|
||||||
// note we need no synchronization here, this variable is only accessed in the ui thread
|
// note we need no synchronization here, this variable is only accessed in the ui thread
|
||||||
mIsCancelled = true;
|
mIsCancelled = true;
|
||||||
|
|
||||||
|
getActivity().setResult(RESULT_CANCELED);
|
||||||
|
getActivity().finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDismiss(DialogInterface dialog) {
|
public void onDismiss(DialogInterface dialog) {
|
||||||
super.onDismiss(dialog);
|
super.onDismiss(dialog);
|
||||||
|
|
||||||
if (getActivity() == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
hideKeyboard();
|
hideKeyboard();
|
||||||
|
|
||||||
getActivity().setResult(RESULT_CANCELED);
|
|
||||||
getActivity().finish();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void hideKeyboard() {
|
private void hideKeyboard() {
|
||||||
@ -495,11 +453,9 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
|||||||
inputManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
|
inputManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Associate the "done" button on the soft keyboard with the okay button in the view
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||||
|
// Associate the "done" button on the soft keyboard with the okay button in the view
|
||||||
if (EditorInfo.IME_ACTION_DONE == actionId) {
|
if (EditorInfo.IME_ACTION_DONE == actionId) {
|
||||||
AlertDialog dialog = ((AlertDialog) getDialog());
|
AlertDialog dialog = ((AlertDialog) getDialog());
|
||||||
Button bt = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
|
Button bt = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
|
||||||
|
@ -300,7 +300,7 @@ public class ViewKeyFragment extends LoaderFragment implements
|
|||||||
* because the notification triggers faster than the activity closes.
|
* because the notification triggers faster than the activity closes.
|
||||||
*/
|
*/
|
||||||
// Avoid NullPointerExceptions...
|
// Avoid NullPointerExceptions...
|
||||||
if (data.getCount() == 0) {
|
if (data == null || data.getCount() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Swap the new cursor in. (The framework will take care of closing the
|
// Swap the new cursor in. (The framework will take care of closing the
|
||||||
|
@ -19,15 +19,16 @@ package org.sufficientlysecure.keychain.ui.adapter;
|
|||||||
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Calendar;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.TimeZone;
|
import java.util.List;
|
||||||
|
|
||||||
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;
|
||||||
@ -60,7 +61,6 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
KeyRings.VERIFIED,
|
KeyRings.VERIFIED,
|
||||||
KeyRings.HAS_ANY_SECRET,
|
KeyRings.HAS_ANY_SECRET,
|
||||||
KeyRings.HAS_DUPLICATE_USER_ID,
|
KeyRings.HAS_DUPLICATE_USER_ID,
|
||||||
KeyRings.HAS_ENCRYPT,
|
|
||||||
KeyRings.FINGERPRINT,
|
KeyRings.FINGERPRINT,
|
||||||
KeyRings.CREATION,
|
KeyRings.CREATION,
|
||||||
};
|
};
|
||||||
@ -72,9 +72,8 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
public static final int INDEX_VERIFIED = 5;
|
public static final int INDEX_VERIFIED = 5;
|
||||||
public static final int INDEX_HAS_ANY_SECRET = 6;
|
public static final int INDEX_HAS_ANY_SECRET = 6;
|
||||||
public static final int INDEX_HAS_DUPLICATE_USER_ID = 7;
|
public static final int INDEX_HAS_DUPLICATE_USER_ID = 7;
|
||||||
public static final int INDEX_HAS_ENCRYPT = 8;
|
public static final int INDEX_FINGERPRINT = 8;
|
||||||
public static final int INDEX_FINGERPRINT = 9;
|
public static final int INDEX_CREATION = 9;
|
||||||
public static final int INDEX_CREATION = 10;
|
|
||||||
|
|
||||||
public KeyAdapter(Context context, Cursor c, int flags) {
|
public KeyAdapter(Context context, Cursor c, int flags) {
|
||||||
super(context, c, flags);
|
super(context, c, flags);
|
||||||
@ -87,6 +86,7 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class KeyItemViewHolder {
|
public static class KeyItemViewHolder {
|
||||||
|
public View mView;
|
||||||
public Long mMasterKeyId;
|
public Long mMasterKeyId;
|
||||||
public TextView mMainUserId;
|
public TextView mMainUserId;
|
||||||
public TextView mMainUserIdRest;
|
public TextView mMainUserIdRest;
|
||||||
@ -96,6 +96,7 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
public ImageButton mSlingerButton;
|
public ImageButton mSlingerButton;
|
||||||
|
|
||||||
public KeyItemViewHolder(View view) {
|
public KeyItemViewHolder(View view) {
|
||||||
|
mView = view;
|
||||||
mMainUserId = (TextView) view.findViewById(R.id.key_list_item_name);
|
mMainUserId = (TextView) view.findViewById(R.id.key_list_item_name);
|
||||||
mMainUserIdRest = (TextView) view.findViewById(R.id.key_list_item_email);
|
mMainUserIdRest = (TextView) view.findViewById(R.id.key_list_item_email);
|
||||||
mStatus = (ImageView) view.findViewById(R.id.key_list_item_status_icon);
|
mStatus = (ImageView) view.findViewById(R.id.key_list_item_status_icon);
|
||||||
@ -104,11 +105,10 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
mCreationDate = (TextView) view.findViewById(R.id.key_list_item_creation);
|
mCreationDate = (TextView) view.findViewById(R.id.key_list_item_creation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setData(Context context, Cursor cursor, Highlighter highlighter) {
|
public void setData(Context context, KeyItem item, Highlighter highlighter) {
|
||||||
|
|
||||||
{ // set name and stuff, common to both key types
|
{ // set name and stuff, common to both key types
|
||||||
String userId = cursor.getString(INDEX_USER_ID);
|
KeyRing.UserId userIdSplit = item.mUserId;
|
||||||
KeyRing.UserId userIdSplit = KeyRing.splitUserId(userId);
|
|
||||||
if (userIdSplit.name != null) {
|
if (userIdSplit.name != null) {
|
||||||
mMainUserId.setText(highlighter.highlight(userIdSplit.name));
|
mMainUserId.setText(highlighter.highlight(userIdSplit.name));
|
||||||
} else {
|
} else {
|
||||||
@ -124,30 +124,23 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
|
|
||||||
{ // set edit button and status, specific by key type
|
{ // set edit button and status, specific by key type
|
||||||
|
|
||||||
long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID);
|
mMasterKeyId = item.mKeyId;
|
||||||
boolean isSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) != 0;
|
|
||||||
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) != 0;
|
|
||||||
|
|
||||||
mMasterKeyId = masterKeyId;
|
|
||||||
|
|
||||||
// Note: order is important!
|
// Note: order is important!
|
||||||
if (isRevoked) {
|
if (item.mIsRevoked) {
|
||||||
KeyFormattingUtils
|
KeyFormattingUtils
|
||||||
.setStatusImage(context, mStatus, null, State.REVOKED, R.color.bg_gray);
|
.setStatusImage(context, mStatus, null, State.REVOKED, R.color.bg_gray);
|
||||||
mStatus.setVisibility(View.VISIBLE);
|
mStatus.setVisibility(View.VISIBLE);
|
||||||
mSlinger.setVisibility(View.GONE);
|
mSlinger.setVisibility(View.GONE);
|
||||||
mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray));
|
mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray));
|
||||||
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray));
|
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray));
|
||||||
} else if (isExpired) {
|
} else if (item.mIsExpired) {
|
||||||
KeyFormattingUtils.setStatusImage(context, mStatus, null, State.EXPIRED, R.color.bg_gray);
|
KeyFormattingUtils.setStatusImage(context, mStatus, null, State.EXPIRED, R.color.bg_gray);
|
||||||
mStatus.setVisibility(View.VISIBLE);
|
mStatus.setVisibility(View.VISIBLE);
|
||||||
mSlinger.setVisibility(View.GONE);
|
mSlinger.setVisibility(View.GONE);
|
||||||
mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray));
|
mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray));
|
||||||
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray));
|
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray));
|
||||||
} else if (isSecret) {
|
} else if (item.mIsSecret) {
|
||||||
mStatus.setVisibility(View.GONE);
|
mStatus.setVisibility(View.GONE);
|
||||||
if (mSlingerButton.hasOnClickListeners()) {
|
if (mSlingerButton.hasOnClickListeners()) {
|
||||||
mSlinger.setVisibility(View.VISIBLE);
|
mSlinger.setVisibility(View.VISIBLE);
|
||||||
@ -158,7 +151,7 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black));
|
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black));
|
||||||
} else {
|
} else {
|
||||||
// this is a public key - show if it's verified
|
// this is a public key - show if it's verified
|
||||||
if (isVerified) {
|
if (item.mIsVerified) {
|
||||||
KeyFormattingUtils.setStatusImage(context, mStatus, State.VERIFIED);
|
KeyFormattingUtils.setStatusImage(context, mStatus, State.VERIFIED);
|
||||||
mStatus.setVisibility(View.VISIBLE);
|
mStatus.setVisibility(View.VISIBLE);
|
||||||
} else {
|
} else {
|
||||||
@ -170,9 +163,9 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black));
|
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasDuplicate) {
|
if (item.mHasDuplicate) {
|
||||||
String dateTime = DateUtils.formatDateTime(context,
|
String dateTime = DateUtils.formatDateTime(context,
|
||||||
cursor.getLong(INDEX_CREATION) * 1000,
|
item.mCreation.getTime(),
|
||||||
DateUtils.FORMAT_SHOW_DATE
|
DateUtils.FORMAT_SHOW_DATE
|
||||||
| DateUtils.FORMAT_SHOW_YEAR
|
| DateUtils.FORMAT_SHOW_YEAR
|
||||||
| DateUtils.FORMAT_ABBREV_MONTH);
|
| DateUtils.FORMAT_ABBREV_MONTH);
|
||||||
@ -190,6 +183,10 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled(Cursor cursor) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||||
View view = mInflater.inflate(R.layout.key_list_item, parent, false);
|
View view = mInflater.inflate(R.layout.key_list_item, parent, false);
|
||||||
@ -204,7 +201,8 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
public void bindView(View view, Context context, Cursor cursor) {
|
public void bindView(View view, Context context, Cursor cursor) {
|
||||||
Highlighter highlighter = new Highlighter(context, mQuery);
|
Highlighter highlighter = new Highlighter(context, mQuery);
|
||||||
KeyItemViewHolder h = (KeyItemViewHolder) view.getTag();
|
KeyItemViewHolder h = (KeyItemViewHolder) view.getTag();
|
||||||
h.setData(context, cursor, highlighter);
|
KeyItem item = new KeyItem(cursor);
|
||||||
|
h.setData(context, item, highlighter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSecretAvailable(int id) {
|
public boolean isSecretAvailable(int id) {
|
||||||
@ -234,8 +232,9 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getItemId(int position) {
|
public long getItemId(int position) {
|
||||||
|
Cursor cursor = getCursor();
|
||||||
// prevent a crash on rapid cursor changes
|
// prevent a crash on rapid cursor changes
|
||||||
if (getCursor().isClosed()) {
|
if (cursor != null && getCursor().isClosed()) {
|
||||||
return 0L;
|
return 0L;
|
||||||
}
|
}
|
||||||
return super.getItemId(position);
|
return super.getItemId(position);
|
||||||
@ -250,6 +249,7 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
public final boolean mHasDuplicate;
|
public final boolean mHasDuplicate;
|
||||||
public final Date mCreation;
|
public final Date mCreation;
|
||||||
public final String mFingerprint;
|
public final String mFingerprint;
|
||||||
|
public final boolean mIsSecret, mIsRevoked, mIsExpired, mIsVerified;
|
||||||
|
|
||||||
private KeyItem(Cursor cursor) {
|
private KeyItem(Cursor cursor) {
|
||||||
String userId = cursor.getString(INDEX_USER_ID);
|
String userId = cursor.getString(INDEX_USER_ID);
|
||||||
@ -260,6 +260,10 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
mCreation = new Date(cursor.getLong(INDEX_CREATION) * 1000);
|
mCreation = new Date(cursor.getLong(INDEX_CREATION) * 1000);
|
||||||
mFingerprint = KeyFormattingUtils.convertFingerprintToHex(
|
mFingerprint = KeyFormattingUtils.convertFingerprintToHex(
|
||||||
cursor.getBlob(INDEX_FINGERPRINT));
|
cursor.getBlob(INDEX_FINGERPRINT));
|
||||||
|
mIsSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) != 0;
|
||||||
|
mIsRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;
|
||||||
|
mIsExpired = cursor.getInt(INDEX_IS_EXPIRED) > 0;
|
||||||
|
mIsVerified = cursor.getInt(INDEX_VERIFIED) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeyItem(CanonicalizedPublicKeyRing ring) {
|
public KeyItem(CanonicalizedPublicKeyRing ring) {
|
||||||
@ -272,6 +276,12 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
mCreation = key.getCreationTime();
|
mCreation = key.getCreationTime();
|
||||||
mFingerprint = KeyFormattingUtils.convertFingerprintToHex(
|
mFingerprint = KeyFormattingUtils.convertFingerprintToHex(
|
||||||
ring.getFingerprint());
|
ring.getFingerprint());
|
||||||
|
mIsRevoked = key.isRevoked();
|
||||||
|
mIsExpired = key.isExpired();
|
||||||
|
|
||||||
|
// these two are actually "don't know"s
|
||||||
|
mIsSecret = false;
|
||||||
|
mIsVerified = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getReadableName() {
|
public String getReadableName() {
|
||||||
@ -284,4 +294,11 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String[] getProjectionWith(String[] projection) {
|
||||||
|
List<String> list = new ArrayList<>();
|
||||||
|
list.addAll(Arrays.asList(PROJECTION));
|
||||||
|
list.addAll(Arrays.asList(projection));
|
||||||
|
return list.toArray(new String[list.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -38,25 +38,16 @@ import org.sufficientlysecure.keychain.util.FabContainer;
|
|||||||
public class Notify {
|
public class Notify {
|
||||||
|
|
||||||
public static enum Style {
|
public static enum Style {
|
||||||
OK, WARN, ERROR;
|
OK (R.color.android_green_light), WARN(R.color.android_orange_light), ERROR(R.color.android_red_light);
|
||||||
|
|
||||||
public void applyToBar(Snackbar bar) {
|
public final int mLineColor;
|
||||||
|
|
||||||
switch (this) {
|
Style(int color) {
|
||||||
case OK:
|
mLineColor = color;
|
||||||
// bar.actionColorResource(R.color.android_green_light);
|
|
||||||
bar.lineColorResource(R.color.android_green_light);
|
|
||||||
break;
|
|
||||||
case WARN:
|
|
||||||
// bar.textColorResource(R.color.android_orange_light);
|
|
||||||
bar.lineColorResource(R.color.android_orange_light);
|
|
||||||
break;
|
|
||||||
case ERROR:
|
|
||||||
// bar.textColorResource(R.color.android_red_light);
|
|
||||||
bar.lineColorResource(R.color.android_red_light);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void applyToBar(Snackbar bar) {
|
||||||
|
bar.lineColorResource(mLineColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,10 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui.widget;
|
package org.sufficientlysecure.keychain.ui.widget;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@ -24,14 +28,11 @@ import android.os.Bundle;
|
|||||||
import android.support.v4.content.CursorLoader;
|
import android.support.v4.content.CursorLoader;
|
||||||
import android.support.v4.content.Loader;
|
import android.support.v4.content.Loader;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.widget.ImageView;
|
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
|
||||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
|
|
||||||
|
|
||||||
public class CertifyKeySpinner extends KeySpinner {
|
public class CertifyKeySpinner extends KeySpinner {
|
||||||
private long mHiddenMasterKeyId = Constants.key.none;
|
private long mHiddenMasterKeyId = Constants.key.none;
|
||||||
@ -59,19 +60,9 @@ public class CertifyKeySpinner extends KeySpinner {
|
|||||||
// sample only has one Loader, so we don't care about the ID.
|
// sample only has one Loader, so we don't care about the ID.
|
||||||
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsUri();
|
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsUri();
|
||||||
|
|
||||||
// These are the rows that we will retrieve.
|
String[] projection = KeyAdapter.getProjectionWith(new String[] {
|
||||||
String[] projection = new String[]{
|
|
||||||
KeychainContract.KeyRings._ID,
|
|
||||||
KeychainContract.KeyRings.MASTER_KEY_ID,
|
|
||||||
KeychainContract.KeyRings.KEY_ID,
|
|
||||||
KeychainContract.KeyRings.USER_ID,
|
|
||||||
KeychainContract.KeyRings.IS_REVOKED,
|
|
||||||
KeychainContract.KeyRings.IS_EXPIRED,
|
|
||||||
KeychainContract.KeyRings.HAS_CERTIFY,
|
KeychainContract.KeyRings.HAS_CERTIFY,
|
||||||
KeychainContract.KeyRings.HAS_ANY_SECRET,
|
});
|
||||||
KeychainContract.KeyRings.HAS_DUPLICATE_USER_ID,
|
|
||||||
KeychainContract.KeyRings.CREATION
|
|
||||||
};
|
|
||||||
|
|
||||||
String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1 AND "
|
String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1 AND "
|
||||||
+ KeychainDatabase.Tables.KEYS + "." + KeychainContract.KeyRings.MASTER_KEY_ID
|
+ KeychainDatabase.Tables.KEYS + "." + KeychainContract.KeyRings.MASTER_KEY_ID
|
||||||
@ -82,7 +73,7 @@ public class CertifyKeySpinner extends KeySpinner {
|
|||||||
return new CursorLoader(getContext(), baseUri, projection, where, null, null);
|
return new CursorLoader(getContext(), baseUri, projection, where, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int mIndexHasCertify, mIndexIsRevoked, mIndexIsExpired;
|
private int mIndexHasCertify;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||||
@ -90,8 +81,6 @@ public class CertifyKeySpinner extends KeySpinner {
|
|||||||
|
|
||||||
if (loader.getId() == LOADER_ID) {
|
if (loader.getId() == LOADER_ID) {
|
||||||
mIndexHasCertify = data.getColumnIndex(KeychainContract.KeyRings.HAS_CERTIFY);
|
mIndexHasCertify = data.getColumnIndex(KeychainContract.KeyRings.HAS_CERTIFY);
|
||||||
mIndexIsRevoked = data.getColumnIndex(KeychainContract.KeyRings.IS_REVOKED);
|
|
||||||
mIndexIsExpired = data.getColumnIndex(KeychainContract.KeyRings.IS_EXPIRED);
|
|
||||||
|
|
||||||
// If:
|
// If:
|
||||||
// - no key has been pre-selected (e.g. by SageSlinger)
|
// - no key has been pre-selected (e.g. by SageSlinger)
|
||||||
@ -119,18 +108,15 @@ public class CertifyKeySpinner extends KeySpinner {
|
|||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean setStatus(Context context, Cursor cursor, ImageView statusView) {
|
boolean isItemEnabled(Cursor cursor) {
|
||||||
if (cursor.getInt(mIndexIsRevoked) != 0) {
|
if (cursor.getInt(KeyAdapter.INDEX_IS_REVOKED) != 0) {
|
||||||
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.REVOKED, R.color.bg_gray);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (cursor.getInt(mIndexIsExpired) != 0) {
|
if (cursor.getInt(KeyAdapter.INDEX_IS_EXPIRED) != 0) {
|
||||||
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.EXPIRED, R.color.bg_gray);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// don't invalidate the "None" entry, which is also null!
|
// don't invalidate the "None" entry, which is also null!
|
||||||
if (cursor.getPosition() != 0 && cursor.isNull(mIndexHasCertify)) {
|
if (cursor.getPosition() != 0 && cursor.isNull(mIndexHasCertify)) {
|
||||||
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.UNAVAILABLE, R.color.bg_gray);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@ import com.tokenautocomplete.TokenCompleteTextView;
|
|||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
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.KeychainDatabase.Tables;
|
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
|
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
|
||||||
@ -126,7 +127,13 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView
|
|||||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||||
// These are the rows that we will retrieve.
|
// These are the rows that we will retrieve.
|
||||||
Uri baseUri = KeyRings.buildUnifiedKeyRingsUri();
|
Uri baseUri = KeyRings.buildUnifiedKeyRingsUri();
|
||||||
String where = KeyRings.HAS_ENCRYPT + " NOT NULL AND " + KeyRings.IS_EXPIRED + " = 0 AND "
|
|
||||||
|
String[] projection = KeyAdapter.getProjectionWith(new String[] {
|
||||||
|
KeychainContract.KeyRings.HAS_ENCRYPT,
|
||||||
|
});
|
||||||
|
|
||||||
|
String where = KeyRings.HAS_ENCRYPT + " NOT NULL AND "
|
||||||
|
+ KeyRings.IS_EXPIRED + " = 0 AND "
|
||||||
+ Tables.KEYS + "." + KeyRings.IS_REVOKED + " = 0";
|
+ Tables.KEYS + "." + KeyRings.IS_REVOKED + " = 0";
|
||||||
|
|
||||||
if (args != null && args.containsKey(ARG_QUERY)) {
|
if (args != null && args.containsKey(ARG_QUERY)) {
|
||||||
@ -135,12 +142,12 @@ 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, projection, where,
|
||||||
new String[]{"%" + query + "%"}, null);
|
new String[]{"%" + query + "%"}, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
mAdapter.setSearchQuery(null);
|
mAdapter.setSearchQuery(null);
|
||||||
return new CursorLoader(getContext(), baseUri, KeyAdapter.PROJECTION, where, null, null);
|
return new CursorLoader(getContext(), baseUri, projection, where, null, null);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,33 +17,30 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui.widget;
|
package org.sufficientlysecure.keychain.ui.widget;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.Color;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.v4.app.FragmentActivity;
|
import android.support.v4.app.FragmentActivity;
|
||||||
import android.support.v4.app.LoaderManager;
|
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.v7.widget.AppCompatSpinner;
|
import android.support.v7.widget.AppCompatSpinner;
|
||||||
import android.text.format.DateUtils;
|
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.BaseAdapter;
|
import android.widget.BaseAdapter;
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.SpinnerAdapter;
|
import android.widget.SpinnerAdapter;
|
||||||
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.pgp.KeyRing;
|
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
|
||||||
|
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use AppCompatSpinner from AppCompat lib instead of Spinner. Fixes white dropdown icon.
|
* Use AppCompatSpinner from AppCompat lib instead of Spinner. Fixes white dropdown icon.
|
||||||
@ -119,7 +116,8 @@ public abstract class KeySpinner extends AppCompatSpinner implements
|
|||||||
if (getContext() instanceof FragmentActivity) {
|
if (getContext() instanceof FragmentActivity) {
|
||||||
((FragmentActivity) getContext()).getSupportLoaderManager().restartLoader(LOADER_ID, null, this);
|
((FragmentActivity) getContext()).getSupportLoaderManager().restartLoader(LOADER_ID, null, this);
|
||||||
} else {
|
} else {
|
||||||
Log.e(Constants.TAG, "KeySpinner must be attached to FragmentActivity, this is " + getContext().getClass());
|
throw new AssertionError("KeySpinner must be attached to FragmentActivity, this is "
|
||||||
|
+ getContext().getClass());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,7 +136,11 @@ public abstract class KeySpinner extends AppCompatSpinner implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
public long getSelectedKeyId() {
|
public long getSelectedKeyId() {
|
||||||
return getSelectedItemId();
|
Object item = getSelectedItem();
|
||||||
|
if (item instanceof KeyItem) {
|
||||||
|
return ((KeyItem) item).mKeyId;
|
||||||
|
}
|
||||||
|
return Constants.key.none;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPreSelectedKeyId(long selectedKeyId) {
|
public void setPreSelectedKeyId(long selectedKeyId) {
|
||||||
@ -146,161 +148,87 @@ public abstract class KeySpinner extends AppCompatSpinner implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected class SelectKeyAdapter extends BaseAdapter implements SpinnerAdapter {
|
protected class SelectKeyAdapter extends BaseAdapter implements SpinnerAdapter {
|
||||||
private CursorAdapter inner;
|
private KeyAdapter inner;
|
||||||
private int mIndexUserId;
|
|
||||||
private int mIndexDuplicate;
|
|
||||||
private int mIndexMasterKeyId;
|
private int mIndexMasterKeyId;
|
||||||
private int mIndexCreationDate;
|
|
||||||
|
|
||||||
public SelectKeyAdapter() {
|
public SelectKeyAdapter() {
|
||||||
inner = new CursorAdapter(getContext(), null, 0) {
|
inner = new KeyAdapter(getContext(), null, 0) {
|
||||||
@Override
|
|
||||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
|
||||||
return View.inflate(getContext(), R.layout.keyspinner_item, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void bindView(View view, Context context, Cursor cursor) {
|
public boolean isEnabled(Cursor cursor) {
|
||||||
TextView vKeyName = (TextView) view.findViewById(R.id.keyspinner_key_name);
|
return KeySpinner.this.isItemEnabled(cursor);
|
||||||
ImageView vKeyStatus = (ImageView) view.findViewById(R.id.keyspinner_key_status);
|
|
||||||
TextView vKeyEmail = (TextView) view.findViewById(R.id.keyspinner_key_email);
|
|
||||||
TextView vDuplicate = (TextView) view.findViewById(R.id.keyspinner_duplicate);
|
|
||||||
|
|
||||||
KeyRing.UserId userId = KeyRing.splitUserId(cursor.getString(mIndexUserId));
|
|
||||||
vKeyName.setText(userId.name);
|
|
||||||
vKeyEmail.setText(userId.email);
|
|
||||||
|
|
||||||
boolean duplicate = cursor.getLong(mIndexDuplicate) > 0;
|
|
||||||
if (duplicate) {
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean valid = setStatus(getContext(), cursor, vKeyStatus);
|
|
||||||
setItemEnabled(view, valid);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getItemId(int position) {
|
|
||||||
try {
|
|
||||||
return ((Cursor) getItem(position)).getLong(mIndexMasterKeyId);
|
|
||||||
} catch (Exception e) {
|
|
||||||
// This can happen on concurrent modification :(
|
|
||||||
return Constants.key.none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setItemEnabled(View view, boolean enabled) {
|
|
||||||
TextView vKeyName = (TextView) view.findViewById(R.id.keyspinner_key_name);
|
|
||||||
ImageView vKeyStatus = (ImageView) view.findViewById(R.id.keyspinner_key_status);
|
|
||||||
TextView vKeyEmail = (TextView) view.findViewById(R.id.keyspinner_key_email);
|
|
||||||
TextView vKeyDuplicate = (TextView) view.findViewById(R.id.keyspinner_duplicate);
|
|
||||||
|
|
||||||
if (enabled) {
|
|
||||||
vKeyName.setTextColor(Color.BLACK);
|
|
||||||
vKeyEmail.setTextColor(Color.BLACK);
|
|
||||||
vKeyDuplicate.setTextColor(Color.BLACK);
|
|
||||||
vKeyStatus.setVisibility(View.GONE);
|
|
||||||
view.setClickable(false);
|
|
||||||
} else {
|
|
||||||
vKeyName.setTextColor(Color.GRAY);
|
|
||||||
vKeyEmail.setTextColor(Color.GRAY);
|
|
||||||
vKeyDuplicate.setTextColor(Color.GRAY);
|
|
||||||
vKeyStatus.setVisibility(View.VISIBLE);
|
|
||||||
// this is a HACK. the trick is, if the element itself is clickable, the
|
|
||||||
// click is not passed on to the view list
|
|
||||||
view.setClickable(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Cursor swapCursor(Cursor newCursor) {
|
public Cursor swapCursor(Cursor newCursor) {
|
||||||
if (newCursor == null) return inner.swapCursor(null);
|
if (newCursor == null) return inner.swapCursor(null);
|
||||||
|
|
||||||
mIndexDuplicate = newCursor.getColumnIndex(KeychainContract.KeyRings.HAS_DUPLICATE_USER_ID);
|
|
||||||
mIndexUserId = newCursor.getColumnIndex(KeychainContract.KeyRings.USER_ID);
|
|
||||||
mIndexMasterKeyId = newCursor.getColumnIndex(KeychainContract.KeyRings.MASTER_KEY_ID);
|
mIndexMasterKeyId = newCursor.getColumnIndex(KeychainContract.KeyRings.MASTER_KEY_ID);
|
||||||
mIndexCreationDate = newCursor.getColumnIndex(KeychainContract.KeyRings.CREATION);
|
|
||||||
|
Cursor oldCursor = inner.swapCursor(newCursor);
|
||||||
|
|
||||||
// pre-select key if mPreSelectedKeyId is given
|
// pre-select key if mPreSelectedKeyId is given
|
||||||
if (mPreSelectedKeyId != Constants.key.none && newCursor.moveToFirst()) {
|
if (mPreSelectedKeyId != Constants.key.none && newCursor.moveToFirst()) {
|
||||||
do {
|
do {
|
||||||
if (newCursor.getLong(mIndexMasterKeyId) == mPreSelectedKeyId) {
|
if (newCursor.getLong(mIndexMasterKeyId) == mPreSelectedKeyId) {
|
||||||
setSelection(newCursor.getPosition() + 1);
|
setSelection(newCursor.getPosition() +1);
|
||||||
}
|
}
|
||||||
} while (newCursor.moveToNext());
|
} while (newCursor.moveToNext());
|
||||||
}
|
}
|
||||||
return inner.swapCursor(newCursor);
|
return oldCursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getCount() {
|
public int getCount() {
|
||||||
return inner.getCount() + 1;
|
return inner.getCount() +1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getItem(int position) {
|
public Object getItem(int position) {
|
||||||
if (position == 0) return null;
|
if (position == 0) {
|
||||||
return inner.getItem(position - 1);
|
return null;
|
||||||
|
}
|
||||||
|
return inner.getItem(position -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getItemId(int position) {
|
public long getItemId(int position) {
|
||||||
if (position == 0) return Constants.key.none;
|
if (position == 0) {
|
||||||
return inner.getItemId(position - 1);
|
return Constants.key.none;
|
||||||
|
}
|
||||||
|
return inner.getItemId(position -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("ViewHolder") // inflate call is for the preview only
|
|
||||||
@Override
|
@Override
|
||||||
public View getView(int position, View convertView, ViewGroup parent) {
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
try {
|
|
||||||
View v = getDropDownView(position, convertView, parent);
|
// Unfortunately, SpinnerAdapter does not support multiple view
|
||||||
v.findViewById(R.id.keyspinner_key_email).setVisibility(View.GONE);
|
// types. For this reason, we throw away convertViews of a bad
|
||||||
return v;
|
// type. This is sort of a hack, but since the number of elements
|
||||||
} catch (NullPointerException e) {
|
// we deal with in KeySpinners is usually very small (number of
|
||||||
// This is for the preview...
|
// secret keys), this is the easiest solution. (I'm sorry.)
|
||||||
return View.inflate(getContext(), android.R.layout.simple_list_item_1, null);
|
if (convertView != null) {
|
||||||
|
// This assumes that the inner view has non-null tags on its views!
|
||||||
|
boolean isWrongType = (convertView.getTag() == null) != (position == 0);
|
||||||
|
if (isWrongType) {
|
||||||
|
convertView = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
if (position > 0) {
|
||||||
public View getDropDownView(int position, View convertView, ViewGroup parent) {
|
return inner.getView(position -1, convertView, parent);
|
||||||
View view;
|
|
||||||
if (position == 0) {
|
|
||||||
if (convertView == null) {
|
|
||||||
view = inner.newView(null, null, parent);
|
|
||||||
} else {
|
|
||||||
view = convertView;
|
|
||||||
}
|
|
||||||
TextView vKeyName = (TextView) view.findViewById(R.id.keyspinner_key_name);
|
|
||||||
ImageView vKeyStatus = (ImageView) view.findViewById(R.id.keyspinner_key_status);
|
|
||||||
TextView vKeyEmail = (TextView) view.findViewById(R.id.keyspinner_key_email);
|
|
||||||
TextView vKeyDuplicate = (TextView) view.findViewById(R.id.keyspinner_duplicate);
|
|
||||||
|
|
||||||
vKeyName.setText(R.string.choice_none);
|
|
||||||
vKeyEmail.setVisibility(View.GONE);
|
|
||||||
vKeyDuplicate.setVisibility(View.GONE);
|
|
||||||
vKeyStatus.setVisibility(View.GONE);
|
|
||||||
setItemEnabled(view, true);
|
|
||||||
} else {
|
|
||||||
view = inner.getView(position - 1, convertView, parent);
|
|
||||||
TextView vKeyEmail = (TextView) view.findViewById(R.id.keyspinner_key_email);
|
|
||||||
vKeyEmail.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean setStatus(Context context, Cursor cursor, ImageView statusView) {
|
return convertView != null ? convertView :
|
||||||
|
LayoutInflater.from(getContext()).inflate(
|
||||||
|
R.layout.keyspinner_item_none, parent, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isItemEnabled(Cursor cursor) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui.widget;
|
package org.sufficientlysecure.keychain.ui.widget;
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@ -24,12 +25,9 @@ import android.os.Bundle;
|
|||||||
import android.support.v4.content.CursorLoader;
|
import android.support.v4.content.CursorLoader;
|
||||||
import android.support.v4.content.Loader;
|
import android.support.v4.content.Loader;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.widget.ImageView;
|
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.R;
|
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
|
||||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
|
|
||||||
|
|
||||||
public class SignKeySpinner extends KeySpinner {
|
public class SignKeySpinner extends KeySpinner {
|
||||||
public SignKeySpinner(Context context) {
|
public SignKeySpinner(Context context) {
|
||||||
@ -50,19 +48,9 @@ public class SignKeySpinner extends KeySpinner {
|
|||||||
// sample only has one Loader, so we don't care about the ID.
|
// sample only has one Loader, so we don't care about the ID.
|
||||||
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsUri();
|
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsUri();
|
||||||
|
|
||||||
// These are the rows that we will retrieve.
|
String[] projection = KeyAdapter.getProjectionWith(new String[] {
|
||||||
String[] projection = new String[]{
|
|
||||||
KeychainContract.KeyRings._ID,
|
|
||||||
KeychainContract.KeyRings.MASTER_KEY_ID,
|
|
||||||
KeychainContract.KeyRings.KEY_ID,
|
|
||||||
KeychainContract.KeyRings.USER_ID,
|
|
||||||
KeychainContract.KeyRings.IS_REVOKED,
|
|
||||||
KeychainContract.KeyRings.IS_EXPIRED,
|
|
||||||
KeychainContract.KeyRings.HAS_SIGN,
|
KeychainContract.KeyRings.HAS_SIGN,
|
||||||
KeychainContract.KeyRings.HAS_ANY_SECRET,
|
});
|
||||||
KeychainContract.KeyRings.HAS_DUPLICATE_USER_ID,
|
|
||||||
KeychainContract.KeyRings.CREATION
|
|
||||||
};
|
|
||||||
|
|
||||||
String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1";
|
String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1";
|
||||||
|
|
||||||
@ -71,7 +59,7 @@ public class SignKeySpinner extends KeySpinner {
|
|||||||
return new CursorLoader(getContext(), baseUri, projection, where, null, null);
|
return new CursorLoader(getContext(), baseUri, projection, where, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int mIndexHasSign, mIndexIsRevoked, mIndexIsExpired;
|
private int mIndexHasSign;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||||
@ -79,23 +67,18 @@ public class SignKeySpinner extends KeySpinner {
|
|||||||
|
|
||||||
if (loader.getId() == LOADER_ID) {
|
if (loader.getId() == LOADER_ID) {
|
||||||
mIndexHasSign = data.getColumnIndex(KeychainContract.KeyRings.HAS_SIGN);
|
mIndexHasSign = data.getColumnIndex(KeychainContract.KeyRings.HAS_SIGN);
|
||||||
mIndexIsRevoked = data.getColumnIndex(KeychainContract.KeyRings.IS_REVOKED);
|
|
||||||
mIndexIsExpired = data.getColumnIndex(KeychainContract.KeyRings.IS_EXPIRED);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean setStatus(Context context, Cursor cursor, ImageView statusView) {
|
boolean isItemEnabled(Cursor cursor) {
|
||||||
if (cursor.getInt(mIndexIsRevoked) != 0) {
|
if (cursor.getInt(KeyAdapter.INDEX_IS_REVOKED) != 0) {
|
||||||
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.REVOKED, R.color.bg_gray);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (cursor.getInt(mIndexIsExpired) != 0) {
|
if (cursor.getInt(KeyAdapter.INDEX_IS_EXPIRED) != 0) {
|
||||||
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.EXPIRED, R.color.bg_gray);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (cursor.getInt(mIndexHasSign) == 0) {
|
if (cursor.getInt(mIndexHasSign) == 0) {
|
||||||
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.UNAVAILABLE, R.color.bg_gray);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse>
|
||||||
|
*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.sufficientlysecure.keychain.ui.widget;
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ViewAnimator;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
|
||||||
|
|
||||||
|
/** This view is essentially identical to ViewAnimator, but allows specifying the initial view
|
||||||
|
* for preview as an xml attribute. */
|
||||||
|
public class ToolableViewAnimator extends ViewAnimator {
|
||||||
|
|
||||||
|
private int mInitChild = -1;
|
||||||
|
|
||||||
|
public ToolableViewAnimator(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs);
|
||||||
|
|
||||||
|
if (isInEditMode()) {
|
||||||
|
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ToolableViewAnimator, defStyleAttr, 0);
|
||||||
|
mInitChild = a.getInt(R.styleable.ToolableViewAnimator_initialView, -1);
|
||||||
|
a.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addView(View child, int index, ViewGroup.LayoutParams params) {
|
||||||
|
if (isInEditMode() && mInitChild-- > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.addView(child, index, params);
|
||||||
|
}
|
||||||
|
}
|
@ -34,7 +34,8 @@
|
|||||||
android:inputType="textPassword"
|
android:inputType="textPassword"
|
||||||
android:hint="@string/label_passphrase"
|
android:hint="@string/label_passphrase"
|
||||||
android:ems="10"
|
android:ems="10"
|
||||||
android:layout_gravity="center_horizontal" />
|
android:layout_gravity="center_horizontal"
|
||||||
|
/>
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/create_key_passphrase_again"
|
android:id="@+id/create_key_passphrase_again"
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
<?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"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
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">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"
|
||||||
android:id="@+id/decrypt_content"
|
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"
|
||||||
@ -28,6 +30,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="top"
|
android:gravity="top"
|
||||||
android:hint=""
|
android:hint=""
|
||||||
|
tools:text="This is the plaintext"
|
||||||
android:textIsSelectable="true" />
|
android:textIsSelectable="true" />
|
||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
<?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="wrap_content"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:descendantFocusability="blocksDescendants"
|
|
||||||
android:focusable="false"
|
|
||||||
android:minHeight="44dip">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dip"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:focusable="true"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingLeft="8dp"
|
|
||||||
android:paddingRight="4dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/keyspinner_key_name"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/label_main_user_id"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/keyspinner_key_email"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:text="user@example.com"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/keyspinner_duplicate"
|
|
||||||
android:text="creation: bla"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/keyspinner_key_status"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:src="@drawable/status_signature_revoked_cutout_24dp"
|
|
||||||
android:paddingLeft="16dp"
|
|
||||||
android:paddingRight="16dp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
19
OpenKeychain/src/main/res/layout/keyspinner_item_none.xml
Normal file
19
OpenKeychain/src/main/res/layout/keyspinner_item_none.xml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?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="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:minHeight="44dip"
|
||||||
|
android:paddingLeft="8dp"
|
||||||
|
android:paddingRight="4dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/keyspinner_key_name"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/choice_none"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
@ -60,6 +60,7 @@
|
|||||||
android:layout_marginRight="48dp"
|
android:layout_marginRight="48dp"
|
||||||
android:layout_marginEnd="48dp"
|
android:layout_marginEnd="48dp"
|
||||||
android:text=""
|
android:text=""
|
||||||
|
tools:text="Alice Skywalker"
|
||||||
android:textColor="@color/icons"
|
android:textColor="@color/icons"
|
||||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
android:layout_above="@+id/view_key_status" />
|
android:layout_above="@+id/view_key_status" />
|
||||||
@ -73,6 +74,7 @@
|
|||||||
android:layout_marginRight="48dp"
|
android:layout_marginRight="48dp"
|
||||||
android:layout_marginEnd="48dp"
|
android:layout_marginEnd="48dp"
|
||||||
android:text=""
|
android:text=""
|
||||||
|
tools:text="My Key"
|
||||||
android:textColor="@color/tab_text"
|
android:textColor="@color/tab_text"
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
android:layout_above="@+id/toolbar2" />
|
android:layout_above="@+id/toolbar2" />
|
||||||
@ -95,6 +97,7 @@
|
|||||||
android:layout_width="64dp"
|
android:layout_width="64dp"
|
||||||
android:layout_height="64dp"
|
android:layout_height="64dp"
|
||||||
android:visibility="invisible"
|
android:visibility="invisible"
|
||||||
|
tools:visibility="visible"
|
||||||
style="?android:attr/borderlessButtonStyle"
|
style="?android:attr/borderlessButtonStyle"
|
||||||
android:src="@drawable/ic_action_encrypt_file_24dp" />
|
android:src="@drawable/ic_action_encrypt_file_24dp" />
|
||||||
|
|
||||||
@ -103,6 +106,7 @@
|
|||||||
android:layout_width="64dp"
|
android:layout_width="64dp"
|
||||||
android:layout_height="64dp"
|
android:layout_height="64dp"
|
||||||
android:visibility="invisible"
|
android:visibility="invisible"
|
||||||
|
tools:visibility="visible"
|
||||||
style="?android:attr/borderlessButtonStyle"
|
style="?android:attr/borderlessButtonStyle"
|
||||||
android:src="@drawable/ic_action_encrypt_text_24dp" />
|
android:src="@drawable/ic_action_encrypt_text_24dp" />
|
||||||
|
|
||||||
@ -111,6 +115,7 @@
|
|||||||
android:layout_width="64dp"
|
android:layout_width="64dp"
|
||||||
android:layout_height="64dp"
|
android:layout_height="64dp"
|
||||||
android:visibility="invisible"
|
android:visibility="invisible"
|
||||||
|
tools:visibility="visible"
|
||||||
style="?android:attr/borderlessButtonStyle"
|
style="?android:attr/borderlessButtonStyle"
|
||||||
android:src="@drawable/ic_nfc_white_24dp" />
|
android:src="@drawable/ic_nfc_white_24dp" />
|
||||||
|
|
||||||
@ -120,6 +125,7 @@
|
|||||||
android:id="@+id/view_key_status_image"
|
android:id="@+id/view_key_status_image"
|
||||||
android:layout_width="96dp"
|
android:layout_width="96dp"
|
||||||
android:visibility="invisible"
|
android:visibility="invisible"
|
||||||
|
tools:visibility="visible"
|
||||||
android:src="@drawable/status_signature_unverified_cutout_96dp"
|
android:src="@drawable/status_signature_unverified_cutout_96dp"
|
||||||
android:layout_height="96dp"
|
android:layout_height="96dp"
|
||||||
android:layout_above="@id/toolbar2"
|
android:layout_above="@id/toolbar2"
|
||||||
@ -139,6 +145,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:foreground="?android:attr/selectableItemBackground"
|
android:foreground="?android:attr/selectableItemBackground"
|
||||||
|
tools:visibility="invisible"
|
||||||
card_view:cardBackgroundColor="@android:color/white"
|
card_view:cardBackgroundColor="@android:color/white"
|
||||||
card_view:cardElevation="2dp"
|
card_view:cardElevation="2dp"
|
||||||
card_view:cardUseCompatPadding="true"
|
card_view:cardUseCompatPadding="true"
|
||||||
@ -147,7 +154,8 @@
|
|||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/view_key_qr_code"
|
android:id="@+id/view_key_qr_code"
|
||||||
android:layout_width="96dp"
|
android:layout_width="96dp"
|
||||||
android:layout_height="96dp" />
|
android:layout_height="96dp"
|
||||||
|
/>
|
||||||
</android.support.v7.widget.CardView>
|
</android.support.v7.widget.CardView>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
@ -189,6 +197,7 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:visibility="invisible"
|
android:visibility="invisible"
|
||||||
|
tools:visibility="visible"
|
||||||
android:elevation="4dp"
|
android:elevation="4dp"
|
||||||
fab:fab_icon="@drawable/ic_qrcode_white_24dp"
|
fab:fab_icon="@drawable/ic_qrcode_white_24dp"
|
||||||
fab:fab_colorNormal="@color/fab"
|
fab:fab_colorNormal="@color/fab"
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
|
<declare-styleable name="ToolableViewAnimator">
|
||||||
|
<attr name="initialView" format="integer" />
|
||||||
|
</declare-styleable>
|
||||||
|
|
||||||
<declare-styleable name="FoldableLinearLayout">
|
<declare-styleable name="FoldableLinearLayout">
|
||||||
<attr name="foldedLabel" format="string" />
|
<attr name="foldedLabel" format="string" />
|
||||||
<attr name="unFoldedLabel" format="string" />
|
<attr name="unFoldedLabel" format="string" />
|
||||||
|
@ -11,6 +11,7 @@ buildscript {
|
|||||||
classpath 'com.novoda:bintray-release:0.2.7'
|
classpath 'com.novoda:bintray-release:0.2.7'
|
||||||
|
|
||||||
classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.0.1'
|
classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.0.1'
|
||||||
|
// classpath 'com.stanfy.spoon:spoon-gradle-plugin:1.0.2'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
25
tools/android-wait-for-emulator
Executable file
25
tools/android-wait-for-emulator
Executable file
@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Originally written by Ralf Kistner <ralf@embarkmobile.com>, but placed in the public domain
|
||||||
|
|
||||||
|
set +e
|
||||||
|
|
||||||
|
bootanim=""
|
||||||
|
failcounter=0
|
||||||
|
timeout_in_sec=720
|
||||||
|
|
||||||
|
until [[ "$bootanim" =~ "stopped" ]]; do
|
||||||
|
bootanim=`adb -e shell getprop init.svc.bootanim 2>&1 &`
|
||||||
|
if [[ "$bootanim" =~ "device not found" || "$bootanim" =~ "device offline"
|
||||||
|
|| "$bootanim" =~ "running" ]]; then
|
||||||
|
let "failcounter += 1"
|
||||||
|
echo "Waiting for emulator to start"
|
||||||
|
if [[ $failcounter -gt timeout_in_sec ]]; then
|
||||||
|
echo "Timeout ($timeout_in_sec seconds) reached; failed to start emulator"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
sleep 10
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Emulator is ready"
|
Loading…
Reference in New Issue
Block a user