Merge pull request #2553 from open-keychain/cleanup-bc

Update to AndroidX, use upstream bouncycastle provider
This commit is contained in:
Vincent Breitmoser 2020-05-31 11:45:17 +02:00 committed by GitHub
commit f3aceb4f12
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
407 changed files with 1103 additions and 6756 deletions

12
.gitmodules vendored
View file

@ -2,18 +2,6 @@
path = extern/openpgp-api-lib
url = https://github.com/open-keychain/openpgp-api.git
ignore = dirty
[submodule "extern/KeybaseLib"]
path = extern/KeybaseLib
url = https://github.com/open-keychain/KeybaseLib.git
ignore = dirty
[submodule "extern/minidns"]
path = extern/minidns
url = https://github.com/open-keychain/minidns.git
ignore = dirty
[submodule "extern/safeslinger-exchange"]
path = extern/safeslinger-exchange
url = https://github.com/open-keychain/exchange-android
ignore = dirty
[submodule "OpenKeychain/src/test/resources/openpgp-interop"]
path = OpenKeychain/src/test/resources/openpgp-interop
url = https://github.com/google/openpgp-interop

View file

@ -1,5 +1,4 @@
apply plugin: 'com.android.application'
apply plugin: 'jacoco'
apply plugin: 'com.squareup.sqldelight'
// apply plugin: 'com.github.kt3k.coveralls'
@ -8,113 +7,94 @@ dependencies {
// NOTE: libraries are pinned to a specific build, see below
// from local Android SDK
compile 'com.android.support:support-v4:27.1.1'
compile 'com.android.support:appcompat-v7:27.1.1'
compile 'com.android.support:design:27.1.1'
compile 'com.android.support:recyclerview-v7:27.1.1'
compile 'com.android.support:cardview-v7:27.1.1'
compile 'com.android.support:support-annotations:27.1.1'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.annotation:annotation:1.1.0'
// JCenter etc.
compile 'com.journeyapps:zxing-android-embedded:3.4.0'
compile 'com.google.zxing:core:3.3.0'
compile 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.1'
compile 'org.sufficientlysecure:donations:2.5'
compile 'com.squareup.okhttp3:okhttp:3.9.1'
compile 'com.squareup.okhttp3:okhttp-urlconnection:3.9.1'
compile 'org.apache.james:apache-mime4j-core:0.8.0'
compile 'org.apache.james:apache-mime4j-dom:0.8.0'
implementation 'com.journeyapps:zxing-android-embedded:3.4.0'
implementation 'com.google.zxing:core:3.3.0'
implementation 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.1'
implementation 'org.sufficientlysecure:donations:2.5'
implementation 'com.squareup.okhttp3:okhttp:3.13.1'
implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.9.1'
implementation 'org.apache.james:apache-mime4j-core:0.8.1'
implementation 'org.apache.james:apache-mime4j-dom:0.8.1'
// UI
compile 'org.sufficientlysecure:html-textview:3.1'
compile 'com.jpardogo.materialtabstrip:library:1.1.1'
compile 'com.getbase:floatingactionbutton:1.10.1'
compile 'com.nispok:snackbar:2.11.0'
compile 'com.cocosw:bottomsheet:1.3.1@aar'
implementation 'org.sufficientlysecure:html-textview:3.1'
implementation 'com.jpardogo.materialtabstrip:library:1.1.1'
implementation 'com.getbase:floatingactionbutton:1.10.1'
implementation 'com.nispok:snackbar:2.11.0'
implementation 'com.cocosw:bottomsheet:1.5.0@aar'
// RecyclerView
compile 'eu.davidea:flexible-adapter:5.0.5'
compile 'eu.davidea:flexible-adapter-ui:1.0.0-b5'
compile 'eu.davidea:flexible-adapter-livedata:1.0.0-b2'
implementation 'eu.davidea:flexible-adapter:5.1.0'
implementation 'eu.davidea:flexible-adapter-ui:1.0.0-b5'
implementation 'eu.davidea:flexible-adapter-livedata:1.0.0-b2'
// Material Drawer
compile 'com.mikepenz:materialdrawer:5.6.0@aar'
compile 'com.mikepenz:fastadapter:1.8.2'
compile 'com.mikepenz:materialize:1.0.0'
compile 'com.mikepenz:iconics-core:2.8.1@aar'
compile 'com.mikepenz:google-material-typeface:2.2.0.3.original@aar'
compile 'com.mikepenz:fontawesome-typeface:4.6.0.3@aar'
compile 'com.mikepenz:community-material-typeface:1.5.54.2@aar'
implementation 'com.mikepenz:materialdrawer:6.1.2@aar'
implementation 'com.mikepenz:fastadapter:3.3.0'
implementation 'com.mikepenz:fastadapter-extensions-expandable:3.3.0'
implementation 'com.mikepenz:materialize:1.2.0'
implementation 'com.mikepenz:iconics-core:3.1.0@aar'
implementation 'com.mikepenz:google-material-typeface:2.2.0.3.original@aar'
implementation 'com.mikepenz:fontawesome-typeface:5.3.1.1@aar'
implementation 'com.mikepenz:community-material-typeface:1.5.54.2@aar'
// Nordpol
compile 'com.fidesmo:nordpol-android:0.1.22'
implementation 'com.fidesmo:nordpol-android:0.1.22'
// piwik
implementation 'org.piwik.sdk:piwik-sdk:3.0.3'
// libs as submodules
implementation project(':libkeychain')
implementation project(':openpgp-api-lib')
implementation project(':nfcsweetspot')
implementation project(':sshauthentication-api')
implementation project(':extern:bouncycastle:core')
implementation project(':extern:bouncycastle:pg')
implementation project(':extern:bouncycastle:prov')
implementation project(':extern:minidns')
implementation project(':KeybaseLib')
implementation project(':safeslinger-exchange')
implementation project(':extern:MaterialChipsInput')
implementation "android.arch.work:work-runtime:1.0.0-alpha02"
// implementation project(':openkeychain:extern:bouncycastle:core')
implementation 'org.bouncycastle:bcprov-jdk15on:1.65.01'
implementation project(':extern:bouncycastle:pg')
// implementation project(':openkeychain:extern:bouncycastle:prov')
implementation 'androidx.work:work-runtime:2.3.4'
// Unit tests in the local JVM with Robolectric
// https://developer.android.com/training/testing/unit-testing/local-unit-tests.html
// http://robolectric.org/getting-started/
// http://www.vogella.com/tutorials/Robolectric/article.html
testCompile 'junit:junit:4.12'
testCompile ('org.robolectric:robolectric:3.6.1') {
testImplementation 'junit:junit:4.12'
testImplementation ('org.robolectric:robolectric:3.8') {
exclude group: 'org.bouncycastle', module: 'bcprov-jdk16'
}
testCompile 'org.mockito:mockito-core:1.10.19'
testImplementation 'org.mockito:mockito-core:2.18.0'
// UI testing with Espresso
// Force usage of support libs in the test app, since they are internally used by the runner module.
// https://github.com/googlesamples/android-testing/blob/master/ui/espresso/BasicSample/app/build.gradle#L28
androidTestCompile 'com.android.support:support-annotations:27.1.1'
androidTestCompile 'com.android.support:appcompat-v7:27.1.1'
androidTestCompile 'com.android.support:design:27.1.1'
androidTestCompile 'com.android.support.test:runner:0.5'
androidTestCompile 'com.android.support.test:rules:0.5'
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.2'
androidTestCompile ('com.android.support.test.espresso:espresso-contrib:2.2.2') {
exclude group: 'com.android.support', module: 'appcompat'
exclude group: 'com.android.support', module: 'support-v4'
exclude module: 'recyclerview-v7'
}
implementation 'com.jakewharton.timber:timber:4.7.1'
compile "com.jakewharton.timber:timber:4.5.1"
implementation 'org.glassfish:javax.annotation:10.0-b28'
api "com.google.auto.value:auto-value-annotations:1.6.5"
annotationProcessor "com.google.auto.value:auto-value:1.6.2"
implementation 'com.ryanharter.auto.value:auto-value-parcel-adapter:0.2.6'
annotationProcessor "com.ryanharter.auto.value:auto-value-parcel:0.2.6"
compile 'org.glassfish:javax.annotation:10.0-b28'
provided "com.google.auto.value:auto-value:1.5"
annotationProcessor "com.google.auto.value:auto-value:1.5"
annotationProcessor "com.ryanharter.auto.value:auto-value-parcel:0.2.5"
compile 'com.ryanharter.auto.value:auto-value-parcel-adapter:0.2.5'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
annotationProcessor 'androidx.lifecycle:lifecycle-compiler:2.2.0'
compile "android.arch.lifecycle:extensions:1.0.0"
annotationProcessor "android.arch.lifecycle:compiler:1.0.0"
compile "android.arch.persistence:db-framework:1.0.0"
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'androidx.sqlite:sqlite-framework:2.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
// for debugging the db. don't enable by default, this will expose the database no the network!
// debugImplementation 'com.amitshekhar.android:debug-db:1.0.3'
}
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
minSdkVersion 15
@ -124,7 +104,7 @@ android {
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 "androidx.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
@ -225,7 +205,7 @@ android {
}
variantFilter { variant ->
if(variant.buildType.name.equals('debug') && variant.getFlavors().get(0).name.equals('google')) {
if(variant.buildType.name == 'debug' && variant.getFlavors().get(0).name == 'google') {
variant.setIgnore(true)
}
}
@ -292,34 +272,6 @@ android {
}
}
task jacocoTestReport(type:JacocoReport, dependsOn: "testFdroidDebugWithTestCoverageUnitTest") {
group = "Reporting"
description = "Generate Jacoco coverage reports"
classDirectories = fileTree(
dir: "${buildDir}/intermediates/classes/fdroid/debugWithTestCoverage",
excludes: ['**/R.class',
'**/R$*.class',
'**/*$ViewInjector*.*',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/*Activity*.*',
'**/*Fragment*.*']
)
sourceDirectories = files("${buildDir.parent}/src/main/java")
additionalSourceDirs = files([
"${buildDir}/generated/source/buildConfig/fdroid/debugWithTestCoverage",
"${buildDir}/generated/source/r/fdroid/debugWithTestCoverage"
])
executionData = fileTree(dir: "${buildDir}/jacoco", include: "**/*.exec")
reports {
xml.enabled true
html.enabled true
}
}
// Fix for: No report file available: [/home/travis/build/open-keychain/open-keychain/OpenKeychain/build/reports/cobertura/coverage.xml, /home/travis/build/open-keychain/open-keychain/OpenKeychain/build/reports/jacoco/test/jacocoTestReport.xml]
// coveralls {
// jacocoReportPath 'build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml'

View file

@ -1,163 +0,0 @@
/*
* 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.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Random;
import android.content.Context;
import android.support.annotation.StringRes;
import android.support.test.espresso.UiController;
import android.support.test.espresso.ViewAction;
import android.support.test.espresso.matcher.ViewMatchers;
import android.view.View;
import com.nispok.snackbar.Snackbar;
import org.hamcrest.Matcher;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow;
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
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.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.hamcrest.CoreMatchers.endsWith;
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withSnackbarLineColor;
public class AndroidTestHelpers {
public static void dismissSnackbar() {
onView(withClassName(endsWith("Snackbar")))
.perform(new ViewAction() {
@Override
public Matcher<View> getConstraints() {
return ViewMatchers.isAssignableFrom(Snackbar.class);
}
@Override
public String getDescription() {
return "dismiss snackbar";
}
@Override
public void perform(UiController uiController, View view) {
((Snackbar) view).dismiss();
}
});
}
public static void checkSnackbar(Style style, @StringRes Integer text) {
onView(withClassName(endsWith("Snackbar")))
.check(matches(withSnackbarLineColor(style.mLineColor)));
if (text != null) {
onView(withClassName(endsWith("Snackbar")))
.check(matches(hasDescendant(withText(text))));
}
}
public static void checkAndDismissSnackbar(Style style, @StringRes Integer text) {
checkSnackbar(style, text);
dismissSnackbar();
}
public static void importKeysFromResource(Context context, String name) throws Exception {
IteratorWithIOThrow<UncachedKeyRing> stream = UncachedKeyRing.fromStream(
getInstrumentation().getContext().getAssets().open(name));
KeyWritableRepository helper = KeyWritableRepository.create(context);
while(stream.hasNext()) {
UncachedKeyRing ring = stream.next();
if (ring.isSecret()) {
helper.saveSecretKeyRing(ring);
} else {
helper.savePublicKeyRing(ring);
}
}
}
public static void copyFiles() throws IOException {
File cacheDir = getInstrumentation().getTargetContext().getFilesDir();
byte[] buf = new byte[256];
for (String filename : FILES) {
File outFile = new File(cacheDir, filename);
if (outFile.exists()) {
continue;
}
InputStream in = new BufferedInputStream(getInstrumentation().getContext().getAssets().open(filename));
OutputStream out = new BufferedOutputStream(new FileOutputStream(outFile));
int len;
while( (len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
}
}
public static final String[] FILES = new String[] { "pa.png", "re.png", "ci.png" };
public static File[] getImageNames() {
File cacheDir = getInstrumentation().getTargetContext().getFilesDir();
File[] ret = new File[FILES.length];
for (int i = 0; i < ret.length; i++) {
ret[i] = new File(cacheDir, FILES[i]);
}
return ret;
}
public static <T> T pickRandom(T[] haystack) {
return haystack[new Random().nextInt(haystack.length)];
}
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();
}
public static void cleanupForTests(Context context) throws Exception {
// KeychainDatabase.getInstance(context).clearDatabase();
// import these two, make sure they're there
importKeysFromResource(context, "x.sec.asc");
// make sure no passphrases are cached
PassphraseCacheService.clearCachedPassphrases(context);
}
}

View file

@ -1,29 +0,0 @@
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);
}
}

View file

@ -1,79 +0,0 @@
/*
* 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.view.View;
import com.tokenautocomplete.TokenCompleteTextView;
import org.hamcrest.Matcher;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
import static android.support.test.InstrumentationRegistry.getTargetContext;
public abstract class CustomActions {
public static ViewAction tokenEncryptViewAddToken(long keyId) throws Exception {
CanonicalizedPublicKeyRing ring =
KeyWritableRepository.create(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);
}
};
}
}

View file

@ -1,74 +0,0 @@
package org.sufficientlysecure.keychain.actions;
import java.util.Collection;
import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.ActivityInfo;
import android.support.test.espresso.UiController;
import android.support.test.espresso.ViewAction;
import android.support.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
import android.support.test.runner.lifecycle.Stage;
import android.view.View;
import org.hamcrest.Matcher;
import static android.support.test.espresso.matcher.ViewMatchers.isRoot;
public class OrientationChangeAction implements ViewAction {
private final int orientation;
private OrientationChangeAction(int orientation) {
this.orientation = orientation;
}
@Override
public Matcher<View> getConstraints() {
return isRoot();
}
@Override
public String getDescription() {
return "change orientation to " + orientation;
}
@Override
public void perform(UiController uiController, View view) {
uiController.loopMainThreadUntilIdle();
final Activity activity = findActivity(view.getContext());
if (activity == null){
throw new IllegalStateException("Could not find the current activity");
}
activity.setRequestedOrientation(orientation);
Collection<Activity> resumedActivities = ActivityLifecycleMonitorRegistry
.getInstance().getActivitiesInStage(Stage.RESUMED);
if (resumedActivities.isEmpty()) {
throw new RuntimeException("Could not change orientation");
}
}
private static Activity findActivity(Context context) {
if (context == null)
return null;
else if (context instanceof Activity)
return (Activity) context;
else if (context instanceof ContextWrapper)
return findActivity(((ContextWrapper) context).getBaseContext());
return null;
}
public static ViewAction orientationLandscape() {
return new OrientationChangeAction(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
public static ViewAction orientationPortrait() {
return new OrientationChangeAction(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
}

View file

@ -1,63 +0,0 @@
/*
* 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.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.widget.ImageView;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
public class BitmapMatcher extends TypeSafeMatcher<View> {
private final Bitmap mBitmap;
public BitmapMatcher(Bitmap bitmap) {
super(View.class);
mBitmap = bitmap;
}
@Override
public boolean matchesSafely(View view) {
if ( !(view instanceof ImageView) ) {
return false;
}
Drawable drawable = ((ImageView) view).getDrawable();
return drawable != null && (drawable instanceof BitmapDrawable)
&& ((BitmapDrawable) drawable).getBitmap().sameAs(mBitmap);
}
@Override
public void describeTo(Description description) {
description.appendText("with equivalent specified bitmap");
}
public static BitmapMatcher withBitmap(Bitmap bitmap) {
return new BitmapMatcher(bitmap);
}
}

View file

@ -1,173 +0,0 @@
/*
* 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.annotation.IdRes;
import android.support.test.espresso.matcher.BoundedMatcher;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.ViewAnimator;
import com.nispok.snackbar.Snackbar;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.sufficientlysecure.keychain.R;
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.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withParent;
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.matcher.DrawableMatcher.withDrawable;
public abstract class CustomMatchers {
public static Matcher<View> withDisplayedChild(final int child) {
return new BoundedMatcher<View, ViewAnimator>(ViewAnimator.class) {
public void describeTo(Description description) {
description.appendText("with displayed child: " + child);
}
@Override
public boolean matchesSafely(ViewAnimator viewAnimator) {
return viewAnimator.getDisplayedChild() == child;
}
};
}
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<RecyclerView.ViewHolder> withKeyHolderId(final long keyId) {
return new BoundedMatcher<RecyclerView.ViewHolder, RecyclerView.ViewHolder>(RecyclerView.ViewHolder.class) {
@Override
public void describeTo(Description description) {
description.appendText("with ViewHolder id: " + keyId);
}
@Override
protected boolean matchesSafely(View item) {
return item.getItemId() == 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;
}
};
}
public static Matcher<View> withRecyclerView(@IdRes int viewId) {
return allOf(isAssignableFrom(RecyclerView.class), withId(viewId));
}
public static Matcher<View> isRecyclerItemView(@IdRes int recyclerId, Matcher<View> specificChildMatcher) {
return allOf(withParent(withRecyclerView(recyclerId)), specificChildMatcher);
}
public static Matcher<View> withEncryptionStatus(boolean encrypted) {
if (encrypted) {
return allOf(
hasDescendant(allOf(
withId(R.id.result_encryption_text), withText(R.string.decrypt_result_encrypted))),
hasDescendant(allOf(
withId(R.id.result_encryption_icon), withDrawable(R.drawable.status_lock_closed_24dp, true)))
);
} else {
return allOf(
hasDescendant(allOf(
withId(R.id.result_encryption_text), withText(R.string.decrypt_result_not_encrypted))),
hasDescendant(allOf(
withId(R.id.result_encryption_icon), withDrawable(R.drawable.status_lock_open_24dp, true)))
);
}
}
public static Matcher<View> withSignatureNone() {
return allOf(
hasDescendant(allOf(
withId(R.id.result_signature_text), withText(R.string.decrypt_result_no_signature))),
hasDescendant(allOf(
withId(R.id.result_signature_icon), withDrawable(R.drawable.status_signature_invalid_cutout_24dp, true))),
hasDescendant(allOf(
withId(R.id.result_signature_layout), not(isDisplayed())))
);
}
public static Matcher<View> withSignatureMyKey() {
return allOf(
hasDescendant(allOf(
withId(R.id.result_signature_text), withText(R.string.decrypt_result_signature_certified))),
hasDescendant(allOf(
withId(R.id.result_signature_icon), withDrawable(R.drawable.status_signature_verified_cutout_24dp, true))),
hasDescendant(allOf(
withId(R.id.result_signature_layout), isDisplayed()))
);
}
}

View file

@ -1,128 +0,0 @@
/*
* 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().sameAs((((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);
}
}

View file

@ -1,74 +0,0 @@
/*
* 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.content.Context;
import android.text.method.TransformationMethod;
import android.view.View;
import android.widget.EditText;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
public class EditTextMatchers {
public static TypeSafeMatcher<View> withError(final int errorResId) {
return new TypeSafeMatcher<View>() {
@Override
public boolean matchesSafely(View view) {
Context context = view.getContext();
if (view instanceof EditText) {
CharSequence error = ((EditText) view).getError();
return error != null && error.equals(context.getString(errorResId));
}
return false;
}
@Override
public void describeTo(Description description) {
description.appendText("EditText with error");
}
};
}
public static TypeSafeMatcher<View> withTransformationMethod(final Class<? extends TransformationMethod> transformationClass) {
return new TypeSafeMatcher<View>() {
@Override
public boolean matchesSafely(View view) {
if (view instanceof EditText) {
TransformationMethod transformation = ((EditText) view).getTransformationMethod();
return transformation != null && transformationClass.isInstance(transformation);
}
return false;
}
@Override
public void describeTo(Description description) {
description.appendText("EditText with transformation method");
}
};
}
}

View file

@ -1,167 +0,0 @@
package org.sufficientlysecure.keychain.remote;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ServiceTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.LargeTest;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openintents.openpgp.IOpenPgpService2;
import org.openintents.openpgp.util.OpenPgpApi;
import org.sufficientlysecure.keychain.R;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import static android.support.test.espresso.Espresso.closeSoftKeyboard;
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.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import static org.sufficientlysecure.keychain.AndroidTestHelpers.cleanupForTests;
@RunWith(AndroidJUnit4.class)
@LargeTest
public class OpenPgpServiceTest {
public static final int ACTIVITY_WAIT_TIME = 2 * 1000;
@Rule
public final ServiceTestRule mServiceRule = new ServiceTestRule();
private OpenPgpApi mApi;
@Before
public void setUp() throws Exception {
Context context = InstrumentationRegistry.getTargetContext();
cleanupForTests(context);
Intent serviceIntent = new Intent(context, OpenPgpService2.class);
IBinder binder = mServiceRule.bindService(serviceIntent);
mApi = new OpenPgpApi(context, IOpenPgpService2.Stub.asInterface(binder));
}
@Test
public void testStuff() throws Exception {
// TODO why does this not ask for general usage permissions?!
{
Intent intent = new Intent();
intent.setAction(OpenPgpApi.ACTION_ENCRYPT);
intent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
intent.putExtra(OpenPgpApi.EXTRA_KEY_IDS, new long[]{0x9D604D2F310716A3L});
ByteArrayInputStream is = new ByteArrayInputStream("swag".getBytes());
ByteArrayOutputStream os = new ByteArrayOutputStream();
Intent result = mApi.executeApi(intent, is, os);
assertThat("result is pending accept",
result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR),
is(OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED));
PendingIntent pi = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
pi.send();
Thread.sleep(ACTIVITY_WAIT_TIME); // Wait for activity to start
onView(withText(R.string.button_allow)).perform(click());
}
byte[] ciphertext;
{
Intent intent = new Intent();
intent.setAction(OpenPgpApi.ACTION_ENCRYPT);
intent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
intent.putExtra(OpenPgpApi.EXTRA_KEY_IDS, new long[]{0x9D604D2F310716A3L});
ByteArrayInputStream is = new ByteArrayInputStream("swag".getBytes());
ByteArrayOutputStream os = new ByteArrayOutputStream();
Intent result = mApi.executeApi(intent, is, os);
assertThat("result is encrypt ok",
result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR),
is(OpenPgpApi.RESULT_CODE_SUCCESS));
ciphertext = os.toByteArray();
}
{ // decrypt
Intent intent = new Intent();
intent.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
ByteArrayInputStream is = new ByteArrayInputStream(ciphertext);
ByteArrayOutputStream os = new ByteArrayOutputStream();
Intent result = mApi.executeApi(intent, is, os);
assertThat("result is pending input",
result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR),
is(OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED));
PendingIntent pi = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
pi.send();
Thread.sleep(ACTIVITY_WAIT_TIME); // Wait for activity to start
onView(withText(R.string.button_allow)).perform(click());
}
{ // decrypt again, this time pending passphrase
Intent intent = new Intent();
intent.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
ByteArrayInputStream is = new ByteArrayInputStream(ciphertext);
ByteArrayOutputStream os = new ByteArrayOutputStream();
Intent result = mApi.executeApi(intent, is, os);
assertThat("result is pending passphrase",
result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR),
is(OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED));
PendingIntent pi = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
pi.send();
Thread.sleep(ACTIVITY_WAIT_TIME); // Wait for activity to start
onView(withId(R.id.passphrase_passphrase)).perform(typeText("x"));
// Needed to correctly execute test on Travis
closeSoftKeyboard();
Thread.sleep(1 * 1000);
onView(withText(R.string.btn_unlock)).perform(click());
}
{ // decrypt again, NOW it should work with passphrase cached =)
Intent intent = new Intent();
intent.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
ByteArrayInputStream is = new ByteArrayInputStream(ciphertext);
ByteArrayOutputStream os = new ByteArrayOutputStream();
Intent result = mApi.executeApi(intent, is, os);
assertThat("result is decrypt ok",
result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR),
is(OpenPgpApi.RESULT_CODE_SUCCESS));
byte[] plaintext = os.toByteArray();
assertThat("decrypted plaintext matches plaintext", new String(plaintext), is("swag"));
}
}
}

View file

@ -1,385 +0,0 @@
/*
* 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.ui;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Instrumentation.ActivityResult;
import android.content.Intent;
import android.net.Uri;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.support.test.espresso.intent.Intents;
import android.support.test.espresso.intent.rule.IntentsTestRule;
import android.widget.AdapterView;
import org.junit.Before;
import org.junit.Rule;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.AndroidTestHelpers;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import java.io.File;
import static android.support.test.InstrumentationRegistry.getInstrumentation;
import static android.support.test.espresso.Espresso.onData;
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.intent.matcher.IntentMatchers.hasAction;
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasCategories;
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasExtra;
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasExtraWithKey;
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasType;
import static android.support.test.espresso.matcher.ViewMatchers.assertThat;
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.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.hasItem;
import static org.hamcrest.CoreMatchers.is;
import static org.sufficientlysecure.keychain.AndroidTestHelpers.checkSnackbar;
import static org.sufficientlysecure.keychain.AndroidTestHelpers.getImageNames;
import static org.sufficientlysecure.keychain.AndroidTestHelpers.importKeysFromResource;
import static org.sufficientlysecure.keychain.AndroidTestHelpers.pickRandom;
import static org.sufficientlysecure.keychain.AndroidTestHelpers.randomString;
import static org.sufficientlysecure.keychain.actions.CustomActions.tokenEncryptViewAddToken;
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.isRecyclerItemView;
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withDisplayedChild;
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withEncryptionStatus;
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyItemId;
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withSignatureMyKey;
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withSignatureNone;
//TODO This test is disabled because it needs to be fixed to work with updated code
//@RunWith(AndroidJUnit4.class)
//@LargeTest
public class AsymmetricFileOperationTests {
@Rule
public final IntentsTestRule<MainActivity> mActivity
= new IntentsTestRule<MainActivity>(MainActivity.class) {
@Override
protected Intent getActivityIntent() {
Intent intent = super.getActivityIntent();
intent.putExtra(MainActivity.EXTRA_SKIP_FIRST_TIME, true);
intent.putExtra(MainActivity.EXTRA_INIT_FRAG, MainActivity.ID_ENCRYPT_DECRYPT);
return intent;
}
};
@Before
public void setUp() throws Exception {
Activity activity = mActivity.getActivity();
AndroidTestHelpers.copyFiles();
// import these two, make sure they're there
importKeysFromResource(activity, "x.sec.asc");
// make sure no passphrases are cached
PassphraseCacheService.clearCachedPassphrases(activity);