mirror of https://github.com/MISP/misp-bump
Merge branch 'enable-androidx' into 'master'
Enable androidx See merge request its-wg/overview/overview-ubo/mispbump!4pull/5/head
commit
09fe9b6858
|
@ -1,6 +1,9 @@
|
||||||
|
# Source: https://github.com/github/gitignore/blob/master/Android.gitignore
|
||||||
|
|
||||||
# Built application files
|
# Built application files
|
||||||
*.apk
|
*.apk
|
||||||
*.ap_
|
*.ap_
|
||||||
|
*.aab
|
||||||
|
|
||||||
# Files for the ART/Dalvik VM
|
# Files for the ART/Dalvik VM
|
||||||
*.dex
|
*.dex
|
||||||
|
@ -12,6 +15,7 @@
|
||||||
bin/
|
bin/
|
||||||
gen/
|
gen/
|
||||||
out/
|
out/
|
||||||
|
release/
|
||||||
|
|
||||||
# Gradle files
|
# Gradle files
|
||||||
.gradle/
|
.gradle/
|
||||||
|
@ -40,17 +44,22 @@ captures/
|
||||||
.idea/assetWizardSettings.xml
|
.idea/assetWizardSettings.xml
|
||||||
.idea/dictionaries
|
.idea/dictionaries
|
||||||
.idea/libraries
|
.idea/libraries
|
||||||
|
# Android Studio 3 in .gitignore file.
|
||||||
.idea/caches
|
.idea/caches
|
||||||
|
.idea/modules.xml
|
||||||
|
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
|
||||||
|
.idea/navEditor.xml
|
||||||
|
|
||||||
# Keystore files
|
# Keystore files
|
||||||
# Uncomment the following line if you do not want to check your keystore files in.
|
# Uncomment the following lines if you do not want to check your keystore files in.
|
||||||
#*.jks
|
#*.jks
|
||||||
|
#*.keystore
|
||||||
|
|
||||||
# External native build folder generated in Android Studio 2.2 and later
|
# External native build folder generated in Android Studio 2.2 and later
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
|
|
||||||
# Google Services (e.g. APIs or Firebase)
|
# Google Services (e.g. APIs or Firebase)
|
||||||
google-services.json
|
# google-services.json
|
||||||
|
|
||||||
# Freeline
|
# Freeline
|
||||||
freeline.py
|
freeline.py
|
||||||
|
@ -64,7 +73,12 @@ fastlane/screenshots
|
||||||
fastlane/test_output
|
fastlane/test_output
|
||||||
fastlane/readme.md
|
fastlane/readme.md
|
||||||
|
|
||||||
# custom
|
# Version control
|
||||||
/gradlew
|
vcs.xml
|
||||||
/gradlew.bat
|
|
||||||
/local.properties
|
# lint
|
||||||
|
lint/intermediates/
|
||||||
|
lint/generated/
|
||||||
|
lint/outputs/
|
||||||
|
lint/tmp/
|
||||||
|
# lint/reports/
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
<component name="ProjectCodeStyleConfiguration">
|
|
||||||
<state>
|
|
||||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
|
||||||
</state>
|
|
||||||
</component>
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
|
||||||
|
</project>
|
|
@ -1,6 +1,44 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
<component name="JavadocGenerationManager">
|
||||||
|
<option name="OUTPUT_DIRECTORY" value="$PROJECT_DIR$/.." />
|
||||||
|
</component>
|
||||||
|
<component name="NullableNotNullManager">
|
||||||
|
<option name="myDefaultNullable" value="org.jetbrains.annotations.Nullable" />
|
||||||
|
<option name="myDefaultNotNull" value="androidx.annotation.RecentlyNonNull" />
|
||||||
|
<option name="myNullables">
|
||||||
|
<value>
|
||||||
|
<list size="10">
|
||||||
|
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
|
||||||
|
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
|
||||||
|
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
|
||||||
|
<item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
|
||||||
|
<item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
|
||||||
|
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
|
||||||
|
<item index="6" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" />
|
||||||
|
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
|
||||||
|
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
|
||||||
|
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
<option name="myNotNulls">
|
||||||
|
<value>
|
||||||
|
<list size="9">
|
||||||
|
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
|
||||||
|
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
|
||||||
|
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
|
||||||
|
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
|
||||||
|
<item index="4" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
|
||||||
|
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" />
|
||||||
|
<item index="6" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
|
||||||
|
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
|
||||||
|
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectModuleManager">
|
|
||||||
<modules>
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/mispbump.iml" filepath="$PROJECT_DIR$/mispbump.iml" />
|
|
||||||
</modules>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RunConfigurationProducerService">
|
||||||
|
<option name="ignoredProducers">
|
||||||
|
<set>
|
||||||
|
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -1,124 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="Palette2">
|
|
||||||
<group name="Swing">
|
|
||||||
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
|
||||||
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
|
|
||||||
</item>
|
|
||||||
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
|
||||||
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
|
|
||||||
</item>
|
|
||||||
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
|
||||||
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
|
|
||||||
</item>
|
|
||||||
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
|
|
||||||
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
|
|
||||||
</item>
|
|
||||||
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
|
||||||
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
|
|
||||||
<initial-values>
|
|
||||||
<property name="text" value="Button" />
|
|
||||||
</initial-values>
|
|
||||||
</item>
|
|
||||||
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
|
||||||
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
|
|
||||||
<initial-values>
|
|
||||||
<property name="text" value="RadioButton" />
|
|
||||||
</initial-values>
|
|
||||||
</item>
|
|
||||||
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
|
||||||
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
|
|
||||||
<initial-values>
|
|
||||||
<property name="text" value="CheckBox" />
|
|
||||||
</initial-values>
|
|
||||||
</item>
|
|
||||||
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
|
||||||
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
|
|
||||||
<initial-values>
|
|
||||||
<property name="text" value="Label" />
|
|
||||||
</initial-values>
|
|
||||||
</item>
|
|
||||||
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
|
||||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
|
||||||
<preferred-size width="150" height="-1" />
|
|
||||||
</default-constraints>
|
|
||||||
</item>
|
|
||||||
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
|
||||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
|
||||||
<preferred-size width="150" height="-1" />
|
|
||||||
</default-constraints>
|
|
||||||
</item>
|
|
||||||
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
|
||||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
|
||||||
<preferred-size width="150" height="-1" />
|
|
||||||
</default-constraints>
|
|
||||||
</item>
|
|
||||||
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
|
||||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
|
||||||
<preferred-size width="150" height="50" />
|
|
||||||
</default-constraints>
|
|
||||||
</item>
|
|
||||||
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
|
||||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
|
||||||
<preferred-size width="150" height="50" />
|
|
||||||
</default-constraints>
|
|
||||||
</item>
|
|
||||||
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
|
||||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
|
||||||
<preferred-size width="150" height="50" />
|
|
||||||
</default-constraints>
|
|
||||||
</item>
|
|
||||||
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
|
||||||
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
|
|
||||||
</item>
|
|
||||||
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
|
||||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
|
||||||
<preferred-size width="150" height="50" />
|
|
||||||
</default-constraints>
|
|
||||||
</item>
|
|
||||||
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
|
||||||
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
|
|
||||||
<preferred-size width="150" height="50" />
|
|
||||||
</default-constraints>
|
|
||||||
</item>
|
|
||||||
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
|
||||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
|
||||||
<preferred-size width="150" height="50" />
|
|
||||||
</default-constraints>
|
|
||||||
</item>
|
|
||||||
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
|
||||||
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
|
|
||||||
<preferred-size width="200" height="200" />
|
|
||||||
</default-constraints>
|
|
||||||
</item>
|
|
||||||
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
|
||||||
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
|
|
||||||
<preferred-size width="200" height="200" />
|
|
||||||
</default-constraints>
|
|
||||||
</item>
|
|
||||||
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
|
||||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
|
|
||||||
</item>
|
|
||||||
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
|
||||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
|
|
||||||
</item>
|
|
||||||
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
|
||||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
|
|
||||||
</item>
|
|
||||||
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
|
||||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
|
|
||||||
</item>
|
|
||||||
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
|
||||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
|
|
||||||
<preferred-size width="-1" height="20" />
|
|
||||||
</default-constraints>
|
|
||||||
</item>
|
|
||||||
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
|
||||||
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
|
|
||||||
</item>
|
|
||||||
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
|
||||||
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
|
|
||||||
</item>
|
|
||||||
</group>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
16
README.md
16
README.md
|
@ -1,3 +1,17 @@
|
||||||
# MISPBump
|
# What does this app?
|
||||||
|
1. Exchange public keys to make following communication private (via QR code)
|
||||||
|
+ Diffie Hellman key exchange
|
||||||
|
2. Exchange information needed to sync two MISP instances
|
||||||
|
3. Upload information to MISP instance.
|
||||||
|
|
||||||
|
# Problems
|
||||||
|
+ Androidx migration - bug in material design dependency (just for editor)
|
||||||
|
+ Loading of self signed certificates (currently not supported)
|
||||||
|
|
||||||
|
# TODOs
|
||||||
|
+ custom password for syncUser
|
||||||
|
+ upload ack screen
|
||||||
|
+ translation
|
||||||
|
|
||||||
|
# MISPBump
|
||||||

|

|
||||||
|
|
|
@ -1,44 +1,53 @@
|
||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android-extensions'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 27
|
compileSdkVersion 28
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "de.overview.wg.its.mispbump"
|
applicationId "lu.circl.mispbump"
|
||||||
minSdkVersion 21
|
minSdkVersion 23
|
||||||
targetSdkVersion 27
|
targetSdkVersion 28
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "1.0"
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
vectorDrawables.useSupportLibrary = true
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
// android
|
||||||
|
implementation 'com.google.android.material:material:1.0.0'
|
||||||
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.1.0-beta01'
|
||||||
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
|
implementation 'androidx.recyclerview:recyclerview:1.0.0'
|
||||||
|
|
||||||
implementation 'com.android.support:appcompat-v7:27.1.1'
|
// retrofit
|
||||||
implementation 'com.android.support:design:27.1.1'
|
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
|
||||||
implementation 'com.android.support:gridlayout-v7:27.1.1'
|
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
|
||||||
implementation 'com.android.support:recyclerview-v7:27.1.1'
|
implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0'
|
||||||
implementation 'com.android.support:cardview-v7:27.1.1'
|
|
||||||
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
|
|
||||||
implementation 'com.android.support:preference-v7:27.1.1'
|
|
||||||
implementation 'com.android.support:preference-v14:27.1.1'
|
|
||||||
|
|
||||||
implementation 'com.google.android.gms:play-services-vision:15.0.2'
|
// barcode reading
|
||||||
|
implementation 'com.google.android.gms:play-services-vision:17.0.2'
|
||||||
|
|
||||||
implementation 'com.android.volley:volley:1.1.0'
|
// barcode generation
|
||||||
implementation 'com.github.kenglxn.QRGen:android:2.5.0'
|
implementation 'com.journeyapps:zxing-android-embedded:3.2.0@aar'
|
||||||
implementation 'org.mongodb:bson:3.8.0'
|
implementation 'com.google.zxing:core:3.4.0'
|
||||||
implementation 'com.google.code.gson:gson:2.8.5'
|
|
||||||
|
|
||||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
implementation 'com.android.support:support-v4:27.1.1'
|
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||||
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
}
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package de.overview.wg.its.mispbump;
|
package lu.circl.mispbump;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.support.test.InstrumentationRegistry;
|
import androidx.test.InstrumentationRegistry;
|
||||||
import android.support.test.runner.AndroidJUnit4;
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
@ -21,6 +21,6 @@ public class ExampleInstrumentedTest {
|
||||||
// Context of the app under test.
|
// Context of the app under test.
|
||||||
Context appContext = InstrumentationRegistry.getTargetContext();
|
Context appContext = InstrumentationRegistry.getTargetContext();
|
||||||
|
|
||||||
assertEquals("de.overview.wg.its.mispbump", appContext.getPackageName());
|
assertEquals("lu.circl.mispbump", appContext.getPackageName());
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,44 +1,47 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="de.overview.wg.its.mispbump">
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
package="lu.circl.mispbump">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.CAMERA"/>
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="false"
|
||||||
android:icon="@mipmap/launcher_handshake_square"
|
android:hardwareAccelerated="true"
|
||||||
android:label="@string/app_name"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:roundIcon="@mipmap/launcher_handshake_round"
|
android:label="@string/app_name"
|
||||||
android:supportsRtl="true"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:theme="@style/AppTheme">
|
android:supportsRtl="true"
|
||||||
<activity
|
android:theme="@style/AppTheme"
|
||||||
android:name=".MainActivity"
|
tools:ignore="GoogleAppIndexingWarning">
|
||||||
android:label="@string/app_name">
|
<activity android:name=".activities.UploadActivity"
|
||||||
<intent-filter>
|
android:configChanges="orientation|screenSize"
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
android:label="Upload"/>
|
||||||
|
<activity android:name=".activities.PreferenceActivity" />
|
||||||
|
<activity android:name=".activities.StartUpActivity">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".SyncActivity"
|
android:name=".activities.HomeActivity"
|
||||||
android:label="Sync"
|
android:label="@string/home" />
|
||||||
android:parentActivityName=".MainActivity"
|
<activity
|
||||||
android:screenOrientation="portrait"
|
android:name=".activities.LoginActivity"
|
||||||
android:theme="@style/AppTheme.Fullscreen">
|
android:label="@string/login" />
|
||||||
</activity>
|
<activity
|
||||||
<activity
|
android:name=".activities.SyncActivity"
|
||||||
android:name=".SyncUploadActivity"
|
android:configChanges="orientation|screenSize"
|
||||||
android:label="@string/upload"/>
|
android:label="@string/sync"
|
||||||
<activity
|
android:parentActivityName=".activities.HomeActivity" />
|
||||||
android:name=".MyOrganisationActivity"
|
<activity
|
||||||
android:label="@string/credentials_activity"
|
android:name=".activities.ProfileActivity"
|
||||||
android:parentActivityName=".MainActivity"/>
|
android:label="Profile"
|
||||||
<activity android:name=".QrSyncActivity">
|
android:parentActivityName=".activities.HomeActivity"
|
||||||
</activity>
|
android:theme="@style/AppTheme.Translucent" />
|
||||||
<activity android:name=".preferences.AppPreferenceActivity">
|
</application>
|
||||||
</activity>
|
|
||||||
</application>
|
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
|
@ -1,191 +0,0 @@
|
||||||
package de.overview.wg.its.mispbump;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.design.widget.FloatingActionButton;
|
|
||||||
import android.support.v4.content.ContextCompat;
|
|
||||||
import android.support.v4.graphics.drawable.DrawableCompat;
|
|
||||||
import android.support.v7.app.AlertDialog;
|
|
||||||
import android.support.v7.app.AppCompatActivity;
|
|
||||||
import android.support.v7.widget.DefaultItemAnimator;
|
|
||||||
import android.support.v7.widget.LinearLayoutManager;
|
|
||||||
import android.support.v7.widget.RecyclerView;
|
|
||||||
import android.support.v7.widget.Toolbar;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.CheckBox;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import de.overview.wg.its.mispbump.adapter.SyncedPartnerAdapter;
|
|
||||||
import de.overview.wg.its.mispbump.auxiliary.PreferenceManager;
|
|
||||||
import de.overview.wg.its.mispbump.model.SyncedPartner;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
|
||||||
|
|
||||||
private List<SyncedPartner> syncedPartnerList = new ArrayList<>();
|
|
||||||
private SyncedPartnerAdapter syncedPartnerAdapter;
|
|
||||||
private TextView emptyPartnerListView;
|
|
||||||
private RecyclerView syncedPartnerRecyclerView;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_main);
|
|
||||||
|
|
||||||
initializeViews();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
|
||||||
getMenuInflater().inflate(R.menu.menu_main, menu);
|
|
||||||
|
|
||||||
// Make icon white (compat limitation in xml)
|
|
||||||
Drawable drawable = menu.findItem(R.id.menu_item_credential_settings).getIcon();
|
|
||||||
drawable = DrawableCompat.wrap(drawable);
|
|
||||||
DrawableCompat.setTint(drawable, ContextCompat.getColor(this, R.color.colorWhite));
|
|
||||||
menu.findItem(R.id.menu_item_credential_settings).setIcon(drawable);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
int id = item.getItemId();
|
|
||||||
|
|
||||||
switch (id) {
|
|
||||||
case R.id.menu_item_credential_settings:
|
|
||||||
startCredentialsActivity();
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case R.id.menu_item_delete_local_data:
|
|
||||||
createSelectDeleteDialog();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void initializeViews() {
|
|
||||||
|
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
|
||||||
setSupportActionBar(toolbar);
|
|
||||||
getSupportActionBar().setDisplayShowTitleEnabled(true);
|
|
||||||
|
|
||||||
FloatingActionButton fab = findViewById(R.id.fab_continue_sync_info);
|
|
||||||
fab.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
startSyncActivity();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
emptyPartnerListView = findViewById(R.id.empty);
|
|
||||||
syncedPartnerRecyclerView = findViewById(R.id.recyclerView);
|
|
||||||
|
|
||||||
syncedPartnerAdapter = new SyncedPartnerAdapter(this, syncedPartnerList);
|
|
||||||
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
|
|
||||||
syncedPartnerRecyclerView.setLayoutManager(mLayoutManager);
|
|
||||||
syncedPartnerRecyclerView.setItemAnimator(new DefaultItemAnimator());
|
|
||||||
syncedPartnerRecyclerView.setAdapter(syncedPartnerAdapter);
|
|
||||||
|
|
||||||
refreshSyncedPartnerList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createSelectDeleteDialog() {
|
|
||||||
|
|
||||||
final PreferenceManager prefs = PreferenceManager.Instance(this);
|
|
||||||
|
|
||||||
AlertDialog.Builder adb = new AlertDialog.Builder(this);
|
|
||||||
|
|
||||||
adb.setTitle("Delete Local Data");
|
|
||||||
adb.setMessage("(Checked items will be deleted)");
|
|
||||||
|
|
||||||
@SuppressLint("InflateParams")
|
|
||||||
View checkBoxView = getLayoutInflater().inflate(R.layout.dialog_select_delete_data, null);
|
|
||||||
|
|
||||||
final CheckBox checkSyncedPartner = checkBoxView.findViewById(R.id.check_synced_partner_list);
|
|
||||||
final CheckBox checkCredentials = checkBoxView.findViewById(R.id.check_credentials);
|
|
||||||
final CheckBox checkUserData = checkBoxView.findViewById(R.id.check_user_preferences);
|
|
||||||
|
|
||||||
adb.setView(checkBoxView);
|
|
||||||
|
|
||||||
adb.setPositiveButton(getResources().getString(R.string.delete), new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
if (checkSyncedPartner.isChecked()) {
|
|
||||||
prefs.clearSyncedInformationPreferences();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (checkCredentials.isChecked()) {
|
|
||||||
prefs.clearCredentialPreferences();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (checkUserData.isChecked()) {
|
|
||||||
prefs.clearUserPreferences();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
adb.setNegativeButton(getResources().getString(android.R.string.cancel), null);
|
|
||||||
|
|
||||||
Dialog d = adb.create();
|
|
||||||
d.getWindow().setWindowAnimations(R.style.DialogAnimation);
|
|
||||||
d.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void refreshSyncedPartnerList() {
|
|
||||||
// todo: uncomment
|
|
||||||
// syncedPartnerList = PreferenceManager.Instance(this).getSyncedPartnerList();
|
|
||||||
|
|
||||||
if (syncedPartnerList == null || syncedPartnerList.size() < 1) {
|
|
||||||
emptyPartnerListView.setVisibility(View.VISIBLE);
|
|
||||||
syncedPartnerRecyclerView.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
emptyPartnerListView.setVisibility(View.GONE);
|
|
||||||
syncedPartnerAdapter.setSyncedPartnerList(syncedPartnerList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startSyncActivity() {
|
|
||||||
|
|
||||||
PreferenceManager preferenceManager = PreferenceManager.Instance(this);
|
|
||||||
|
|
||||||
if (preferenceManager.getMyOrganisation() == null || preferenceManager.getMyUser() == null) {
|
|
||||||
|
|
||||||
AlertDialog.Builder adb = new AlertDialog.Builder(this);
|
|
||||||
adb.setTitle(getResources().getString(R.string.missing_local_information_title));
|
|
||||||
adb.setMessage(getResources().getString(R.string.missing_local_information_message));
|
|
||||||
|
|
||||||
adb.setPositiveButton(getResources().getString(R.string.enter_credentials), new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
startCredentialsActivity();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
adb.setNegativeButton(android.R.string.cancel, null);
|
|
||||||
adb.show();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
startActivity(new Intent(this, QrSyncActivity.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startCredentialsActivity() {
|
|
||||||
startActivity(new Intent(this, MyOrganisationActivity.class));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,333 +0,0 @@
|
||||||
package de.overview.wg.its.mispbump;
|
|
||||||
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.support.design.widget.FloatingActionButton;
|
|
||||||
import android.support.design.widget.Snackbar;
|
|
||||||
import android.support.design.widget.TextInputLayout;
|
|
||||||
import android.support.v7.app.AlertDialog;
|
|
||||||
import android.support.v7.app.AppCompatActivity;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.v7.widget.DefaultItemAnimator;
|
|
||||||
import android.support.v7.widget.LinearLayoutManager;
|
|
||||||
import android.support.v7.widget.RecyclerView;
|
|
||||||
import android.support.v7.widget.Toolbar;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.CheckBox;
|
|
||||||
import android.widget.ProgressBar;
|
|
||||||
import com.android.volley.VolleyError;
|
|
||||||
import de.overview.wg.its.mispbump.adapter.OrganisationInfoEntryAdapter;
|
|
||||||
import de.overview.wg.its.mispbump.auxiliary.PreferenceManager;
|
|
||||||
import de.overview.wg.its.mispbump.auxiliary.ReadableError;
|
|
||||||
import de.overview.wg.its.mispbump.model.Organisation;
|
|
||||||
import de.overview.wg.its.mispbump.model.StringPair;
|
|
||||||
import de.overview.wg.its.mispbump.model.User;
|
|
||||||
import de.overview.wg.its.mispbump.network.MispRequest;
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class MyOrganisationActivity extends AppCompatActivity implements View.OnClickListener {
|
|
||||||
|
|
||||||
private PreferenceManager preferenceManager;
|
|
||||||
private RecyclerView recyclerView;
|
|
||||||
private OrganisationInfoEntryAdapter adapter;
|
|
||||||
private View empty;
|
|
||||||
private ProgressBar progressBar;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_my_organisation);
|
|
||||||
|
|
||||||
initializeContent();
|
|
||||||
loadMyInformation();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
|
|
||||||
int id = v.getId();
|
|
||||||
|
|
||||||
switch (id) {
|
|
||||||
case R.id.fab_download:
|
|
||||||
|
|
||||||
enterCredentialsDialog();
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
|
||||||
getMenuInflater().inflate(R.menu.menu_my_org, menu);
|
|
||||||
return super.onCreateOptionsMenu(menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
|
|
||||||
int id = item.getItemId();
|
|
||||||
|
|
||||||
switch (id) {
|
|
||||||
|
|
||||||
case R.id.menu_delete_local_data:
|
|
||||||
deleteLocalDataDialog();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case R.id.load_config:
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT > 25) {
|
|
||||||
preferenceManager.setServerUrl("http://192.168.178.200");
|
|
||||||
preferenceManager.setAutomationKey("d2UEstcJiySUWpsaeiXnEFGoI1xcWhAEIiVgZOmW");
|
|
||||||
} else {
|
|
||||||
preferenceManager.setServerUrl("http://192.168.178.201");
|
|
||||||
preferenceManager.setAutomationKey("eCcz0TTLEc8MeZihsoyyeqlYpd4V8PCDsDA4tM75");
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void initializeContent() {
|
|
||||||
|
|
||||||
Toolbar t = findViewById(R.id.toolbar);
|
|
||||||
setSupportActionBar(t);
|
|
||||||
|
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
getSupportActionBar().setDisplayShowTitleEnabled(true);
|
|
||||||
|
|
||||||
empty = findViewById(R.id.empty);
|
|
||||||
progressBar = findViewById(R.id.progressBar);
|
|
||||||
|
|
||||||
adapter = new OrganisationInfoEntryAdapter(this);
|
|
||||||
|
|
||||||
recyclerView = findViewById(R.id.recyclerView);
|
|
||||||
recyclerView.setLayoutManager(new LinearLayoutManager(this));
|
|
||||||
recyclerView.setItemAnimator(new DefaultItemAnimator());
|
|
||||||
recyclerView.setAdapter(adapter);
|
|
||||||
|
|
||||||
FloatingActionButton fab = findViewById(R.id.fab_download);
|
|
||||||
fab.setOnClickListener(this);
|
|
||||||
|
|
||||||
preferenceManager = PreferenceManager.Instance(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void storeCredentials(String url, String automationKey, boolean saveAutomationKey) {
|
|
||||||
|
|
||||||
if (saveAutomationKey) {
|
|
||||||
preferenceManager.setAutomationKey(automationKey);
|
|
||||||
} else {
|
|
||||||
preferenceManager.setAutomationKey("");
|
|
||||||
}
|
|
||||||
|
|
||||||
preferenceManager.setServerUrl(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void storeMyInformation(Organisation org, User user) {
|
|
||||||
|
|
||||||
if (org != null) {
|
|
||||||
preferenceManager.setMyOrganisation(org);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user != null) {
|
|
||||||
preferenceManager.setMyUser(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadMyInformation() {
|
|
||||||
|
|
||||||
empty.setVisibility(View.VISIBLE);
|
|
||||||
recyclerView.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
Organisation myOrg = preferenceManager.getMyOrganisation();
|
|
||||||
User myUser = preferenceManager.getMyUser();
|
|
||||||
|
|
||||||
visualizeInformation(myOrg, myUser);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void downloadOrganisationInformation(String url, String automationKey) {
|
|
||||||
|
|
||||||
empty.setVisibility(View.GONE);
|
|
||||||
recyclerView.setVisibility(View.GONE);
|
|
||||||
progressBar.setVisibility(View.VISIBLE);
|
|
||||||
|
|
||||||
final MispRequest mispRequest = MispRequest.Instance(this, false);
|
|
||||||
mispRequest.setServerCredentials(url, automationKey);
|
|
||||||
|
|
||||||
final User myUser = new User();
|
|
||||||
final Organisation myOrganisation = new Organisation();
|
|
||||||
|
|
||||||
mispRequest.getMyUser(new MispRequest.UserCallback() {
|
|
||||||
@Override
|
|
||||||
public void onResult(JSONObject jsonUser) {
|
|
||||||
try {
|
|
||||||
|
|
||||||
myUser.fromJSON(jsonUser);
|
|
||||||
|
|
||||||
} catch (JSONException e) {
|
|
||||||
|
|
||||||
alert(e.getMessage());
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mispRequest.getOrganisation(myUser.getId(), new MispRequest.OrganisationCallback() {
|
|
||||||
@Override
|
|
||||||
public void onResult(JSONObject organisationInformation) {
|
|
||||||
try {
|
|
||||||
|
|
||||||
myOrganisation.fromJSON(organisationInformation);
|
|
||||||
|
|
||||||
storeMyInformation(myOrganisation, myUser);
|
|
||||||
visualizeInformation(myOrganisation, myUser);
|
|
||||||
|
|
||||||
} catch (JSONException e) {
|
|
||||||
|
|
||||||
alert(e.getMessage());
|
|
||||||
visualizeInformation(null, null);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(VolleyError volleyError) {
|
|
||||||
alert(ReadableError.toReadable(volleyError));
|
|
||||||
visualizeInformation(null, null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(VolleyError volleyError) {
|
|
||||||
alert(ReadableError.toReadable(volleyError));
|
|
||||||
visualizeInformation(null, null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void visualizeInformation(Organisation org, User user) {
|
|
||||||
|
|
||||||
progressBar.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
if (org != null && user != null) {
|
|
||||||
|
|
||||||
empty.setVisibility(View.GONE);
|
|
||||||
recyclerView.setVisibility(View.VISIBLE);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
empty.setVisibility(View.VISIBLE);
|
|
||||||
recyclerView.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
getSupportActionBar().setTitle(org.getName());
|
|
||||||
|
|
||||||
List<StringPair> orgInfoEntries = new ArrayList<>();
|
|
||||||
|
|
||||||
orgInfoEntries.add(new StringPair("Description", org.getDescription()));
|
|
||||||
orgInfoEntries.add(new StringPair("Nationality", org.getNationality()));
|
|
||||||
orgInfoEntries.add(new StringPair("Sector", org.getSector()));
|
|
||||||
orgInfoEntries.add(new StringPair("User Count", "" + org.getUserCount()));
|
|
||||||
orgInfoEntries.add(new StringPair("Email", user.getEmail()));
|
|
||||||
orgInfoEntries.add(new StringPair("UUID", org.getUuid()));
|
|
||||||
|
|
||||||
adapter.setList(orgInfoEntries);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void enterCredentialsDialog() {
|
|
||||||
|
|
||||||
AlertDialog.Builder adb = new AlertDialog.Builder(this);
|
|
||||||
LayoutInflater inflater = getLayoutInflater();
|
|
||||||
|
|
||||||
adb.setTitle("MISP Credentials");
|
|
||||||
|
|
||||||
View v = inflater.inflate(R.layout.dialog_enter_credentials, null);
|
|
||||||
adb.setView(v);
|
|
||||||
|
|
||||||
final CheckBox saveAutomationKey = v.findViewById(R.id.check_save_authkey);
|
|
||||||
final TextInputLayout serverUrlLayout = v.findViewById(R.id.input_layout_server_url);
|
|
||||||
final TextInputLayout automationKeyLayout = v.findViewById(R.id.input_layout_automation_key);
|
|
||||||
|
|
||||||
saveAutomationKey.setChecked(preferenceManager.saveAuthKeyEnabled());
|
|
||||||
serverUrlLayout.getEditText().setText(preferenceManager.getMyServerUrl());
|
|
||||||
automationKeyLayout.getEditText().setText(preferenceManager.getMyServerAutomationKey());
|
|
||||||
|
|
||||||
adb.setPositiveButton("Download", null);
|
|
||||||
adb.setNegativeButton(android.R.string.cancel, null);
|
|
||||||
|
|
||||||
final Dialog dialog = adb.create();
|
|
||||||
dialog.show();
|
|
||||||
|
|
||||||
Button posButton = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
|
|
||||||
|
|
||||||
posButton.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
String url = serverUrlLayout.getEditText().getText().toString();
|
|
||||||
String automationKey = automationKeyLayout.getEditText().getText().toString();
|
|
||||||
|
|
||||||
boolean validCredentials = true;
|
|
||||||
|
|
||||||
if (url.equals("")) {
|
|
||||||
validCredentials = false;
|
|
||||||
serverUrlLayout.setError(getString(R.string.error_url_required));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (automationKey.equals("")) {
|
|
||||||
validCredentials = false;
|
|
||||||
automationKeyLayout.setError(getString(R.string.error_automation_key));
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean save = saveAutomationKey.isChecked();
|
|
||||||
preferenceManager.setSaveAuthKeyEnabled(save);
|
|
||||||
|
|
||||||
if (validCredentials) {
|
|
||||||
dialog.dismiss();
|
|
||||||
storeCredentials(url, automationKey, save);
|
|
||||||
downloadOrganisationInformation(url, automationKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deleteLocalDataDialog() {
|
|
||||||
|
|
||||||
AlertDialog.Builder adb = new AlertDialog.Builder(this);
|
|
||||||
|
|
||||||
adb.setTitle("Delete Local Data");
|
|
||||||
|
|
||||||
adb.setMessage(getString(R.string.delete_local_data_msg));
|
|
||||||
|
|
||||||
adb.setPositiveButton(getString(R.string.delete), new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
preferenceManager.clearCredentialPreferences();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
adb.setNegativeButton(android.R.string.cancel, null);
|
|
||||||
|
|
||||||
adb.create().show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void alert(String message) {
|
|
||||||
Snackbar.make(findViewById(R.id.coordinator), message, Snackbar.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,456 +0,0 @@
|
||||||
package de.overview.wg.its.mispbump;
|
|
||||||
|
|
||||||
import android.animation.Animator;
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.graphics.Point;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.design.widget.FloatingActionButton;
|
|
||||||
import android.support.v4.app.FragmentManager;
|
|
||||||
import android.support.v4.app.FragmentTransaction;
|
|
||||||
import android.support.v7.app.AppCompatActivity;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.*;
|
|
||||||
import android.view.animation.DecelerateInterpolator;
|
|
||||||
import android.widget.ImageButton;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import de.overview.wg.its.mispbump.auxiliary.AESSecurity;
|
|
||||||
import de.overview.wg.its.mispbump.auxiliary.PreferenceManager;
|
|
||||||
import de.overview.wg.its.mispbump.auxiliary.RandomString;
|
|
||||||
import de.overview.wg.its.mispbump.auxiliary.TempAuth;
|
|
||||||
import de.overview.wg.its.mispbump.cam.CameraFragment;
|
|
||||||
import de.overview.wg.its.mispbump.model.*;
|
|
||||||
import net.glxn.qrgen.android.QRCode;
|
|
||||||
import org.json.JSONException;
|
|
||||||
|
|
||||||
public class QrSyncActivity extends AppCompatActivity implements View.OnClickListener {
|
|
||||||
|
|
||||||
private enum ScanState {
|
|
||||||
public_key,
|
|
||||||
information
|
|
||||||
}
|
|
||||||
|
|
||||||
private ScanState currentScanState;
|
|
||||||
|
|
||||||
private FloatingActionButton proceedToSyncInfoFab, proceedToSyncUploadFab;
|
|
||||||
private PreferenceManager preferenceManager;
|
|
||||||
private View qrBackground;
|
|
||||||
private ImageView qrImage;
|
|
||||||
private CameraFragment cameraFragment;
|
|
||||||
private AESSecurity cryptography;
|
|
||||||
|
|
||||||
private SyncInformationQr receivedSyncInfo;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
|
||||||
setContentView(R.layout.activity_qr_sync);
|
|
||||||
|
|
||||||
initializeContent();
|
|
||||||
startPublicKeyExchange();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
|
|
||||||
int id = v.getId();
|
|
||||||
|
|
||||||
switch (id) {
|
|
||||||
|
|
||||||
case R.id.fab_continue_sync_info:
|
|
||||||
acceptProceedDialog(new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
startSyncInformationExchange();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case R.id.fab_continue_sync_upload:
|
|
||||||
|
|
||||||
acceptProceedDialog(new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
startSyncUpload();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case R.id.close:
|
|
||||||
finish();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void initializeContent() {
|
|
||||||
|
|
||||||
proceedToSyncInfoFab = findViewById(R.id.fab_continue_sync_info);
|
|
||||||
proceedToSyncInfoFab.hide();
|
|
||||||
proceedToSyncInfoFab.setOnClickListener(this);
|
|
||||||
|
|
||||||
proceedToSyncUploadFab = findViewById(R.id.fab_continue_sync_upload);
|
|
||||||
proceedToSyncUploadFab.hide();
|
|
||||||
proceedToSyncUploadFab.setOnClickListener(this);
|
|
||||||
|
|
||||||
ImageButton close = findViewById(R.id.close);
|
|
||||||
close.setOnClickListener(this);
|
|
||||||
|
|
||||||
qrBackground = findViewById(R.id.qr_background);
|
|
||||||
qrBackground.setVisibility(View.INVISIBLE);
|
|
||||||
qrImage = findViewById(R.id.qr_imageView);
|
|
||||||
|
|
||||||
preferenceManager = PreferenceManager.Instance(this);
|
|
||||||
cryptography = AESSecurity.getInstance();
|
|
||||||
|
|
||||||
cameraFragment = new CameraFragment();
|
|
||||||
FragmentManager manager = getSupportFragmentManager();
|
|
||||||
FragmentTransaction transaction = manager.beginTransaction();
|
|
||||||
String camTag = cameraFragment.getClass().getSimpleName();
|
|
||||||
transaction.replace(R.id.fragment_container, cameraFragment, camTag);
|
|
||||||
transaction.commit();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startPublicKeyExchange() {
|
|
||||||
|
|
||||||
currentScanState = ScanState.public_key;
|
|
||||||
cameraFragment.setReadQrEnabled(true);
|
|
||||||
|
|
||||||
TextView info = findViewById(R.id.qr_info);
|
|
||||||
info.setText(getText(R.string.public_key));
|
|
||||||
|
|
||||||
|
|
||||||
User myUser = preferenceManager.getMyUser();
|
|
||||||
Organisation myOrg = preferenceManager.getMyOrganisation();
|
|
||||||
String pubKey = AESSecurity.publicKeyToString(cryptography.getPublicKey());
|
|
||||||
|
|
||||||
PublicKeyQr publicKeyQr = new PublicKeyQr(myOrg.getName(), myUser.getEmail(), pubKey);
|
|
||||||
|
|
||||||
showQr(publicKeyQr.toJSON().toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void receivedPublicKey(PublicKeyQr publicKeyQr) {
|
|
||||||
cryptography.setForeignPublicKey(AESSecurity.publicKeyFromString(publicKeyQr.getKey()));
|
|
||||||
|
|
||||||
runOnUiThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
proceedToSyncInfoFab.show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startSyncInformationExchange() {
|
|
||||||
|
|
||||||
currentScanState = ScanState.information;
|
|
||||||
cameraFragment.setReadQrEnabled(true);
|
|
||||||
|
|
||||||
TextView info = findViewById(R.id.qr_info);
|
|
||||||
info.setText(getString(R.string.sync_information));
|
|
||||||
|
|
||||||
Organisation myOrg = preferenceManager.getMyOrganisation();
|
|
||||||
|
|
||||||
proceedToSyncInfoFab.setVisibility(View.GONE);
|
|
||||||
cameraFragment.setReadQrEnabled(true);
|
|
||||||
|
|
||||||
TempAuth.TMP_AUTH_KEY = new RandomString(40).nextString();
|
|
||||||
|
|
||||||
Server serverForMeOnOtherInstance = new Server();
|
|
||||||
serverForMeOnOtherInstance.setAuthkey(TempAuth.TMP_AUTH_KEY);
|
|
||||||
serverForMeOnOtherInstance.setName("SyncServer for " + myOrg.getName());
|
|
||||||
serverForMeOnOtherInstance.setUrl(preferenceManager.getMyServerUrl());
|
|
||||||
|
|
||||||
final SyncInformationQr siqr = new SyncInformationQr(
|
|
||||||
preferenceManager.getMyOrganisation(),
|
|
||||||
serverForMeOnOtherInstance,
|
|
||||||
preferenceManager.getMyUser());
|
|
||||||
|
|
||||||
|
|
||||||
showQr(cryptography.encrypt(siqr.toJSON().toString()));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void receivedSyncInformation(SyncInformationQr syncInformationQr) {
|
|
||||||
|
|
||||||
receivedSyncInfo = syncInformationQr;
|
|
||||||
|
|
||||||
runOnUiThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
proceedToSyncUploadFab.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onReadQrCode(String qrData) {
|
|
||||||
|
|
||||||
switch (currentScanState) {
|
|
||||||
case public_key:
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
publicKeyReceivedDialog(new PublicKeyQr(qrData));
|
|
||||||
|
|
||||||
} catch (JSONException e) {
|
|
||||||
notExpectedFormatDialog();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case information:
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
syncInformationReceivedDialog(new SyncInformationQr(cryptography.decrypt(qrData)));
|
|
||||||
|
|
||||||
} catch (JSONException e) {
|
|
||||||
notExpectedFormatDialog();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
cameraFragment.setReadQrEnabled(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showQr(String qrData) {
|
|
||||||
generateQr(qrData);
|
|
||||||
|
|
||||||
if (qrBackground.getVisibility() == View.VISIBLE) { // First close if visible
|
|
||||||
circularReveal(qrBackground, false, 300, 0); // close directly
|
|
||||||
circularReveal(qrBackground, true, 300, 350); // open 250ms later
|
|
||||||
} else {
|
|
||||||
circularReveal(qrBackground, true, 300, 0); // if not visible just open directly
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void hideQr() {
|
|
||||||
if (qrBackground.getVisibility() == View.VISIBLE) {
|
|
||||||
circularReveal(qrBackground, false, 200, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void generateQr(String data) {
|
|
||||||
Display display = getWindowManager().getDefaultDisplay();
|
|
||||||
Point size = new Point();
|
|
||||||
display.getSize(size);
|
|
||||||
|
|
||||||
int width = (int) (size.x * 0.8f);
|
|
||||||
|
|
||||||
//noinspection SuspiciousNameCombination
|
|
||||||
qrImage.setImageBitmap(QRCode.from(data)
|
|
||||||
.withColor(0xFF000000, 0x00FFFFFF)
|
|
||||||
.withSize(width, width)
|
|
||||||
.bitmap());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startSyncUpload() {
|
|
||||||
Intent i = new Intent(this, SyncUploadActivity.class);
|
|
||||||
i.putExtra(SyncUploadActivity.PARTNER_INFO_BUNDLE_KEY, new Gson().toJson(receivedSyncInfo));
|
|
||||||
startActivity(i);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void publicKeyReceivedDialog(final PublicKeyQr pkqr) {
|
|
||||||
|
|
||||||
AlertDialog.Builder adb = new AlertDialog.Builder(this);
|
|
||||||
LayoutInflater inflater = getLayoutInflater();
|
|
||||||
|
|
||||||
@SuppressLint("InflateParams")
|
|
||||||
View title = inflater.inflate(R.layout.dialog_public_key, null);
|
|
||||||
adb.setCustomTitle(title);
|
|
||||||
|
|
||||||
View pkInfoView = inflater.inflate(R.layout.view_pk_info, null);
|
|
||||||
|
|
||||||
TextView name = pkInfoView.findViewById(R.id.pk_info_organisation_name);
|
|
||||||
name.setText(pkqr.getOrganisation());
|
|
||||||
|
|
||||||
TextView email = pkInfoView.findViewById(R.id.pk_info_email);
|
|
||||||
email.setText(pkqr.getEmail());
|
|
||||||
|
|
||||||
adb.setView(pkInfoView);
|
|
||||||
|
|
||||||
adb.setPositiveButton(getString(R.string.accept), new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
receivedPublicKey(pkqr);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
adb.setNegativeButton(getString(R.string.reject), new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
cameraFragment.setReadQrEnabled(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
adb.setCancelable(false);
|
|
||||||
|
|
||||||
Dialog d = adb.create();
|
|
||||||
|
|
||||||
//noinspection ConstantConditions
|
|
||||||
d.getWindow().setWindowAnimations(R.style.DialogAnimation);
|
|
||||||
d.getWindow().setDimAmount(0.8f);
|
|
||||||
d.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void syncInformationReceivedDialog(final SyncInformationQr siqr) {
|
|
||||||
|
|
||||||
AlertDialog.Builder adb = new AlertDialog.Builder(this);
|
|
||||||
LayoutInflater inflater = getLayoutInflater();
|
|
||||||
|
|
||||||
@SuppressLint("InflateParams")
|
|
||||||
View title = inflater.inflate(R.layout.dialog_sync_info, null);
|
|
||||||
adb.setCustomTitle(title);
|
|
||||||
|
|
||||||
@SuppressLint("InflateParams")
|
|
||||||
View orgView = inflater.inflate(R.layout.view_organisation, null);
|
|
||||||
|
|
||||||
TextView orgTitle = orgView.findViewById(R.id.organisation_title);
|
|
||||||
orgTitle.setText(siqr.getOrganisation().getName());
|
|
||||||
|
|
||||||
TextView orgUuid = orgView.findViewById(R.id.organisation_uuid);
|
|
||||||
orgUuid.setText(siqr.getOrganisation().getUuid());
|
|
||||||
|
|
||||||
TextView orgDesc = orgView.findViewById(R.id.organisation_description);
|
|
||||||
orgDesc.setText(siqr.getOrganisation().getDescription());
|
|
||||||
|
|
||||||
TextView orgNat = orgView.findViewById(R.id.organisation_nationality);
|
|
||||||
orgNat.setText(siqr.getOrganisation().getNationality());
|
|
||||||
|
|
||||||
TextView orgSec = orgView.findViewById(R.id.organisation_sector);
|
|
||||||
orgSec.setText(siqr.getOrganisation().getSector());
|
|
||||||
|
|
||||||
TextView orgUser = orgView.findViewById(R.id.organisation_user_count);
|
|
||||||
orgUser.setText("" + siqr.getOrganisation().getUserCount());
|
|
||||||
|
|
||||||
adb.setView(orgView);
|
|
||||||
|
|
||||||
adb.setPositiveButton(getString(R.string.accept), new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
receivedSyncInformation(siqr);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
adb.setNegativeButton(getString(R.string.reject), new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
cameraFragment.setReadQrEnabled(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Dialog d = adb.create();
|
|
||||||
//noinspection ConstantConditions
|
|
||||||
d.getWindow().setWindowAnimations(R.style.DialogAnimation);
|
|
||||||
d.getWindow().setDimAmount(0.8f);
|
|
||||||
|
|
||||||
d.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void acceptProceedDialog(Dialog.OnClickListener posListener) {
|
|
||||||
|
|
||||||
AlertDialog.Builder adb = new AlertDialog.Builder(this);
|
|
||||||
|
|
||||||
adb.setTitle(getString(R.string.proceed));
|
|
||||||
|
|
||||||
if (currentScanState == ScanState.public_key) {
|
|
||||||
adb.setMessage(getString(R.string.request_scanned_pk));
|
|
||||||
} else {
|
|
||||||
adb.setMessage(getString(R.string.request_scanned_si));
|
|
||||||
}
|
|
||||||
|
|
||||||
adb.setPositiveButton(getString(R.string.yes), posListener);
|
|
||||||
adb.setNegativeButton(getString(R.string.no), null);
|
|
||||||
|
|
||||||
adb.create().show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void notExpectedFormatDialog() {
|
|
||||||
|
|
||||||
AlertDialog.Builder adb = new AlertDialog.Builder(this);
|
|
||||||
|
|
||||||
switch (currentScanState) {
|
|
||||||
case public_key:
|
|
||||||
adb.setTitle("Public Key Expected");
|
|
||||||
adb.setMessage("Please tell your Sync Partner to go back to the Public Key exchange");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case information:
|
|
||||||
adb.setTitle("Sync Information Expected");
|
|
||||||
adb.setMessage("Please tell your Sync Partner to proceed to the Sync Information exchange");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
adb.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
cameraFragment.setReadQrEnabled(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void circularReveal(final View v, final boolean open, final long duration, final long startDelay) {
|
|
||||||
|
|
||||||
v.post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
int cx = v.getWidth() / 2;
|
|
||||||
int cy = v.getHeight() / 2;
|
|
||||||
|
|
||||||
float finalRadius = (float) Math.hypot(cx, cy);
|
|
||||||
|
|
||||||
Animator anim;
|
|
||||||
|
|
||||||
if (open) {
|
|
||||||
anim = ViewAnimationUtils.createCircularReveal(v, cx, cy, 0, finalRadius);
|
|
||||||
} else {
|
|
||||||
anim = ViewAnimationUtils.createCircularReveal(v, cx, cy, finalRadius, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
anim.setInterpolator(new DecelerateInterpolator());
|
|
||||||
anim.setDuration(duration);
|
|
||||||
|
|
||||||
anim.addListener(new Animator.AnimatorListener() {
|
|
||||||
@Override
|
|
||||||
public void onAnimationStart(Animator animation) {
|
|
||||||
qrBackground.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationEnd(Animator animation) {
|
|
||||||
if (!open) {
|
|
||||||
qrBackground.setVisibility(View.INVISIBLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationCancel(Animator animation) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationRepeat(Animator animation) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
anim.setStartDelay(startDelay);
|
|
||||||
|
|
||||||
anim.start();
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,470 +0,0 @@
|
||||||
package de.overview.wg.its.mispbump;
|
|
||||||
|
|
||||||
import android.support.design.widget.FloatingActionButton;
|
|
||||||
import android.support.v7.app.AppCompatActivity;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.v7.widget.DefaultItemAnimator;
|
|
||||||
import android.support.v7.widget.LinearLayoutManager;
|
|
||||||
import android.support.v7.widget.RecyclerView;
|
|
||||||
import android.support.v7.widget.Toolbar;
|
|
||||||
import android.view.View;
|
|
||||||
import com.android.volley.VolleyError;
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import de.overview.wg.its.mispbump.adapter.UploadStateAdapter;
|
|
||||||
import de.overview.wg.its.mispbump.auxiliary.PreferenceManager;
|
|
||||||
import de.overview.wg.its.mispbump.auxiliary.ReadableError;
|
|
||||||
import de.overview.wg.its.mispbump.auxiliary.TempAuth;
|
|
||||||
import de.overview.wg.its.mispbump.model.*;
|
|
||||||
import de.overview.wg.its.mispbump.network.MispRequest;
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class SyncUploadActivity extends AppCompatActivity implements View.OnClickListener {
|
|
||||||
|
|
||||||
static final String PARTNER_INFO_BUNDLE_KEY = "partner_info";
|
|
||||||
|
|
||||||
private FloatingActionButton fabStart, fabFinish, fabRetry;
|
|
||||||
|
|
||||||
private MispRequest mispRequest;
|
|
||||||
|
|
||||||
private Organisation partnerOrganisation;
|
|
||||||
private Server partnerServer;
|
|
||||||
private User partnerSyncUser;
|
|
||||||
|
|
||||||
private UploadStateAdapter uploadStateAdapter;
|
|
||||||
private UploadState[] uploadStates;
|
|
||||||
private int currentTask = 0;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_upload);
|
|
||||||
|
|
||||||
initializeContent();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
int id = v.getId();
|
|
||||||
|
|
||||||
if (id == R.id.fab_start) {
|
|
||||||
startUpload();
|
|
||||||
fabStart.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id == R.id.fab_retry) {
|
|
||||||
fabRetry.setVisibility(View.GONE);
|
|
||||||
fabFinish.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
//TODO retry implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id == R.id.fab_finish) {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void initializeContent() {
|
|
||||||
|
|
||||||
// Toolbar
|
|
||||||
|
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
|
||||||
setSupportActionBar(toolbar);
|
|
||||||
|
|
||||||
getSupportActionBar().setDisplayShowHomeEnabled(false);
|
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
|
||||||
getSupportActionBar().setDisplayShowTitleEnabled(true);
|
|
||||||
|
|
||||||
// FABs
|
|
||||||
|
|
||||||
fabStart = findViewById(R.id.fab_start);
|
|
||||||
fabStart.setVisibility(View.VISIBLE);
|
|
||||||
fabStart.setOnClickListener(this);
|
|
||||||
|
|
||||||
fabFinish = findViewById(R.id.fab_finish);
|
|
||||||
fabFinish.setVisibility(View.GONE);
|
|
||||||
fabFinish.setOnClickListener(this);
|
|
||||||
|
|
||||||
fabRetry = findViewById(R.id.fab_retry);
|
|
||||||
fabRetry.setVisibility(View.GONE);
|
|
||||||
fabRetry.setOnClickListener(this);
|
|
||||||
|
|
||||||
// RecyclerView
|
|
||||||
|
|
||||||
RecyclerView recyclerView = findViewById(R.id.recyclerView);
|
|
||||||
uploadStateAdapter = new UploadStateAdapter();
|
|
||||||
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(this);
|
|
||||||
recyclerView.setLayoutManager(mLayoutManager);
|
|
||||||
recyclerView.setItemAnimator(new DefaultItemAnimator());
|
|
||||||
recyclerView.setAdapter(uploadStateAdapter);
|
|
||||||
|
|
||||||
// UploadStates
|
|
||||||
|
|
||||||
uploadStates = new UploadState[6];
|
|
||||||
|
|
||||||
uploadStates[0] = new UploadState("Validate upload information");
|
|
||||||
uploadStates[1] = new UploadState("Check connection to server");
|
|
||||||
uploadStates[2] = new UploadState("Create local organisation");
|
|
||||||
uploadStates[3] = new UploadState("Create sync user / add to organisation");
|
|
||||||
uploadStates[4] = new UploadState("Create external organisation");
|
|
||||||
uploadStates[5] = new UploadState("Create sync server");
|
|
||||||
|
|
||||||
uploadStateAdapter.setStates(uploadStates);
|
|
||||||
|
|
||||||
// Request
|
|
||||||
|
|
||||||
mispRequest = MispRequest.Instance(this, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startUpload() {
|
|
||||||
currentTask = 0;
|
|
||||||
executeTask(currentTask);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void undoTask(int index) {
|
|
||||||
switch (index) {
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
createOrganisation(uploadStates[index], true);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
createSyncUser(uploadStates[index], true);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 4:
|
|
||||||
createExternalOrganisation(uploadStates[index], true);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 5:
|
|
||||||
createSyncServer(uploadStates[index], true);
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void executeTask(int index) {
|
|
||||||
|
|
||||||
switch (index) {
|
|
||||||
case 0:
|
|
||||||
checkBundle(uploadStates[index]);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
checkConnection(uploadStates[index]);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
createOrganisation(uploadStates[index], false);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
createSyncUser(uploadStates[index], false);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 4:
|
|
||||||
createExternalOrganisation(uploadStates[index], false);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 5:
|
|
||||||
createSyncServer(uploadStates[index], false);
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadStateAdapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void executeNextTask() {
|
|
||||||
|
|
||||||
currentTask++;
|
|
||||||
|
|
||||||
if (currentTask > uploadStates.length) {
|
|
||||||
addToSyncedList();
|
|
||||||
}
|
|
||||||
|
|
||||||
executeTask(currentTask);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setApplicationError(boolean canRetry) {
|
|
||||||
|
|
||||||
setErrorOnRemainingTasks();
|
|
||||||
|
|
||||||
uploadStateAdapter.notifyDataSetChanged();
|
|
||||||
|
|
||||||
fabFinish.setVisibility(View.VISIBLE);
|
|
||||||
|
|
||||||
if (canRetry) {
|
|
||||||
fabRetry.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setErrorOnRemainingTasks() {
|
|
||||||
|
|
||||||
boolean errorFound = false;
|
|
||||||
|
|
||||||
for(UploadState state : uploadStates) {
|
|
||||||
|
|
||||||
if (!errorFound && state.getCurrentState() == UploadState.State.ERROR) {
|
|
||||||
errorFound = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (errorFound) {
|
|
||||||
state.setFollowError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload States
|
|
||||||
|
|
||||||
private void checkBundle(UploadState state) {
|
|
||||||
state.setInProgress();
|
|
||||||
|
|
||||||
state.setDone();
|
|
||||||
executeNextTask();
|
|
||||||
|
|
||||||
if(true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Bundle b = getIntent().getExtras();
|
|
||||||
|
|
||||||
if (b != null) {
|
|
||||||
|
|
||||||
String info = b.getString(PARTNER_INFO_BUNDLE_KEY);
|
|
||||||
|
|
||||||
SyncInformationQr partnerInformation = new Gson().fromJson(info, SyncInformationQr.class);
|
|
||||||
|
|
||||||
partnerOrganisation = partnerInformation.getOrganisation();
|
|
||||||
partnerServer = partnerInformation.getServer();
|
|
||||||
partnerSyncUser = partnerInformation.getUser();
|
|
||||||
|
|
||||||
if (partnerOrganisation == null || partnerServer == null || partnerSyncUser == null) {
|
|
||||||
state.setError("Partners information format is incorrect");
|
|
||||||
setApplicationError(false);
|
|
||||||
} else {
|
|
||||||
state.setDone();
|
|
||||||
executeNextTask();
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
state.setError("Partners information format is incorrect");
|
|
||||||
setApplicationError(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkConnection(final UploadState state) {
|
|
||||||
state.setInProgress();
|
|
||||||
state.setDone();
|
|
||||||
executeNextTask();
|
|
||||||
|
|
||||||
if(true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mispRequest.testConnection(new MispRequest.ConnectionCallback() {
|
|
||||||
@Override
|
|
||||||
public void onResult(boolean connected) {
|
|
||||||
if (connected) {
|
|
||||||
state.setDone();
|
|
||||||
executeNextTask();
|
|
||||||
} else {
|
|
||||||
state.setError("Could not connect to server");
|
|
||||||
setApplicationError(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createOrganisation(final UploadState state, boolean undo) {
|
|
||||||
|
|
||||||
state.setInProgress();
|
|
||||||
state.setDone();
|
|
||||||
executeNextTask();
|
|
||||||
|
|
||||||
if (true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!undo) {
|
|
||||||
mispRequest.addOrganisation(partnerOrganisation, new MispRequest.OrganisationCallback() {
|
|
||||||
@Override
|
|
||||||
public void onResult(JSONObject organisationInformation) {
|
|
||||||
try {
|
|
||||||
|
|
||||||
partnerSyncUser.setOrgId(new Organisation(organisationInformation).getId());
|
|
||||||
|
|
||||||
state.setDone();
|
|
||||||
executeNextTask();
|
|
||||||
|
|
||||||
} catch (JSONException e) {
|
|
||||||
state.setError("Unknown error: could not read server response");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(VolleyError volleyError) {
|
|
||||||
state.setError(ReadableError.toReadable(volleyError));
|
|
||||||
setApplicationError(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
mispRequest.removeOrganisation(partnerOrganisation.getId(), new MispRequest.DeleteCallback() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess() {
|
|
||||||
state.setDone();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(VolleyError volleyError) {
|
|
||||||
state.setError(ReadableError.toReadable(volleyError));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createSyncUser(final UploadState state, boolean undo) {
|
|
||||||
|
|
||||||
state.setInProgress();
|
|
||||||
state.setDone();
|
|
||||||
executeNextTask();
|
|
||||||
if (true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
partnerSyncUser.setAuthkey(TempAuth.TMP_AUTH_KEY);
|
|
||||||
partnerSyncUser.setRoleId(User.RoleId.SYNC_USER);
|
|
||||||
|
|
||||||
if (!undo) {
|
|
||||||
mispRequest.addUser(partnerSyncUser, new MispRequest.UserCallback() {
|
|
||||||
@Override
|
|
||||||
public void onResult(JSONObject myUserInformation) {
|
|
||||||
state.setDone();
|
|
||||||
executeNextTask();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(VolleyError volleyError) {
|
|
||||||
state.setError(ReadableError.toReadable(volleyError));
|
|
||||||
setApplicationError(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
mispRequest.removeUser(partnerSyncUser.getId(), new MispRequest.DeleteCallback() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess() {
|
|
||||||
state.setDone();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(VolleyError volleyError) {
|
|
||||||
state.setError(ReadableError.toReadable(volleyError));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createExternalOrganisation(final UploadState state, boolean undo) {
|
|
||||||
|
|
||||||
|
|
||||||
state.setInProgress();
|
|
||||||
|
|
||||||
// executeNextTask();
|
|
||||||
if (true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final String originalOrgName = partnerOrganisation.getName();
|
|
||||||
|
|
||||||
if (!undo) {
|
|
||||||
partnerOrganisation.setName(partnerOrganisation.getName() + " (Remote)");
|
|
||||||
partnerOrganisation.setLocal(false);
|
|
||||||
|
|
||||||
mispRequest.addOrganisation(partnerOrganisation, new MispRequest.OrganisationCallback() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResult(JSONObject organisationInformation) {
|
|
||||||
try {
|
|
||||||
|
|
||||||
int extOrgId = new Organisation(organisationInformation).getId();
|
|
||||||
partnerServer.setRemoteOrgId(extOrgId);
|
|
||||||
partnerServer.setPush(true);
|
|
||||||
|
|
||||||
// Reset partner organisation name because it will show as (remote) name in syncedList
|
|
||||||
partnerOrganisation.setName(originalOrgName);
|
|
||||||
|
|
||||||
state.setDone();
|
|
||||||
executeNextTask();
|
|
||||||
|
|
||||||
} catch (JSONException e) {
|
|
||||||
state.setError("Could not interpret server response");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(VolleyError volleyError) {
|
|
||||||
state.setError(ReadableError.toReadable(volleyError));
|
|
||||||
setApplicationError(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
mispRequest.removeOrganisation(partnerOrganisation.getId(), new MispRequest.DeleteCallback() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess() {
|
|
||||||
state.setDone();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(VolleyError volleyError) {
|
|
||||||
state.setError(ReadableError.toReadable(volleyError));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createSyncServer(final UploadState state, boolean undo) {
|
|
||||||
state.setInProgress();
|
|
||||||
|
|
||||||
if (true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!undo) {
|
|
||||||
mispRequest.addServer(partnerServer, new MispRequest.ServerCallback() {
|
|
||||||
@Override
|
|
||||||
public void onResult(JSONObject servers) {
|
|
||||||
state.setDone();
|
|
||||||
executeNextTask();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(VolleyError volleyError) {
|
|
||||||
state.setError(ReadableError.toReadable(volleyError));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addToSyncedList() {
|
|
||||||
|
|
||||||
if(true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PreferenceManager preferenceManager = PreferenceManager.Instance(this);
|
|
||||||
|
|
||||||
List<SyncedPartner> syncedPartnerList = preferenceManager.getSyncedPartnerList();
|
|
||||||
|
|
||||||
SyncedPartner sp = new SyncedPartner(partnerOrganisation.getName(), partnerServer.getUrl());
|
|
||||||
sp.generateTimeStamp();
|
|
||||||
|
|
||||||
syncedPartnerList.add(sp);
|
|
||||||
preferenceManager.setSyncedPartnerList(syncedPartnerList);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,76 +0,0 @@
|
||||||
package de.overview.wg.its.mispbump.adapter;
|
|
||||||
|
|
||||||
import android.content.ClipData;
|
|
||||||
import android.content.ClipboardManager;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.design.widget.Snackbar;
|
|
||||||
import android.support.v7.widget.RecyclerView;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
import de.overview.wg.its.mispbump.model.StringPair;
|
|
||||||
import de.overview.wg.its.mispbump.R;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class OrganisationInfoEntryAdapter extends RecyclerView.Adapter<OrganisationInfoEntryAdapter.MyViewHolder> {
|
|
||||||
|
|
||||||
private Context context;
|
|
||||||
private List<StringPair> list = new ArrayList<>();
|
|
||||||
|
|
||||||
class MyViewHolder extends RecyclerView.ViewHolder {
|
|
||||||
|
|
||||||
View container;
|
|
||||||
TextView title, value;
|
|
||||||
|
|
||||||
private MyViewHolder(View view) {
|
|
||||||
super(view);
|
|
||||||
|
|
||||||
this.title = view.findViewById(R.id.title);
|
|
||||||
this.value = view.findViewById(R.id.value);
|
|
||||||
this.container = view.findViewById(R.id.container);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public OrganisationInfoEntryAdapter(Context context) {
|
|
||||||
this.context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
|
||||||
View row = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_org_info_entry, parent, false);
|
|
||||||
return new OrganisationInfoEntryAdapter.MyViewHolder(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(@NonNull MyViewHolder holder, final int position) {
|
|
||||||
holder.title.setText(list.get(position).key);
|
|
||||||
holder.value.setText(list.get(position).value);
|
|
||||||
|
|
||||||
holder.container.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
ClipData data = ClipData.newPlainText(list.get(position).key, list.get(position).value);
|
|
||||||
ClipboardManager m = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
|
||||||
m.setPrimaryClip(data);
|
|
||||||
|
|
||||||
Toast.makeText(context, context.getText(R.string.copied_to_clipboard), Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return list.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setList(List<StringPair> list) {
|
|
||||||
this.list = list;
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,92 +0,0 @@
|
||||||
package de.overview.wg.its.mispbump.adapter;
|
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.v7.widget.CardView;
|
|
||||||
import android.support.v7.widget.RecyclerView;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import de.overview.wg.its.mispbump.R;
|
|
||||||
import de.overview.wg.its.mispbump.model.SyncedPartner;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class SyncedPartnerAdapter extends RecyclerView.Adapter<SyncedPartnerAdapter.MyViewHolder> {
|
|
||||||
|
|
||||||
private List<SyncedPartner> syncedPartnerList;
|
|
||||||
private Context context;
|
|
||||||
|
|
||||||
class MyViewHolder extends RecyclerView.ViewHolder {
|
|
||||||
|
|
||||||
CardView cardView;
|
|
||||||
TextView title, dateAdded, url;
|
|
||||||
|
|
||||||
MyViewHolder(View view) {
|
|
||||||
super(view);
|
|
||||||
|
|
||||||
cardView = view.findViewById(R.id.card_synced_org);
|
|
||||||
title = view.findViewById(R.id.title);
|
|
||||||
dateAdded = view.findViewById(R.id.dateSynced);
|
|
||||||
url = view.findViewById(R.id.url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public SyncedPartnerAdapter(Context context, List<SyncedPartner> syncedPartnerList) {
|
|
||||||
this.syncedPartnerList = syncedPartnerList;
|
|
||||||
this.context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSyncedPartnerList(List<SyncedPartner> syncedPartnerList) {
|
|
||||||
this.syncedPartnerList = syncedPartnerList;
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
|
||||||
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_synced_organisation, parent, false);
|
|
||||||
return new MyViewHolder(itemView);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(@NonNull final MyViewHolder holder, int position) {
|
|
||||||
final SyncedPartner syncedPartner = syncedPartnerList.get(position);
|
|
||||||
|
|
||||||
holder.title.setText(syncedPartner.getName());
|
|
||||||
holder.url.setText(syncedPartner.getUrl());
|
|
||||||
holder.dateAdded.setText(syncedPartner.getSyncDate());
|
|
||||||
|
|
||||||
holder.cardView.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
|
|
||||||
AlertDialog.Builder adb = new AlertDialog.Builder(context);
|
|
||||||
|
|
||||||
adb.setTitle(context.getString(R.string.dialog_open_browser_title));
|
|
||||||
adb.setMessage(context.getString(R.string.dialog_open_in_browser_msg, syncedPartner.getUrl()));
|
|
||||||
|
|
||||||
adb.setPositiveButton(context.getString(R.string.open), new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
Intent browser = new Intent(Intent.ACTION_VIEW, Uri.parse(syncedPartner.getUrl()));
|
|
||||||
context.startActivity(browser);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
adb.setNegativeButton(context.getString(android.R.string.cancel), null);
|
|
||||||
adb.create().show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return syncedPartnerList.size();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,102 +0,0 @@
|
||||||
package de.overview.wg.its.mispbump.adapter;
|
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.v7.widget.RecyclerView;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.ProgressBar;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import de.overview.wg.its.mispbump.R;
|
|
||||||
import de.overview.wg.its.mispbump.model.UploadState;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class UploadStateAdapter extends RecyclerView.Adapter<UploadStateAdapter.MyViewHolder> {
|
|
||||||
|
|
||||||
private UploadState[] states;
|
|
||||||
|
|
||||||
class MyViewHolder extends RecyclerView.ViewHolder {
|
|
||||||
|
|
||||||
private TextView title, error;
|
|
||||||
private ImageView pendingIcon, errorIcon, doneIcon;
|
|
||||||
private ProgressBar progressBar;
|
|
||||||
|
|
||||||
private MyViewHolder(View view) {
|
|
||||||
|
|
||||||
super(view);
|
|
||||||
|
|
||||||
title = view.findViewById(R.id.title);
|
|
||||||
error = view.findViewById(R.id.state_error_text);
|
|
||||||
|
|
||||||
pendingIcon = view.findViewById(R.id.state_pending);
|
|
||||||
errorIcon = view.findViewById(R.id.state_error);
|
|
||||||
doneIcon = view.findViewById(R.id.state_done);
|
|
||||||
progressBar = view.findViewById(R.id.state_in_progress);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setState(UploadState.State state) {
|
|
||||||
|
|
||||||
error.setVisibility(View.GONE);
|
|
||||||
errorIcon.setVisibility(View.GONE);
|
|
||||||
pendingIcon.setVisibility(View.GONE);
|
|
||||||
doneIcon.setVisibility(View.GONE);
|
|
||||||
progressBar.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
switch (state) {
|
|
||||||
case PENDING:
|
|
||||||
pendingIcon.setVisibility(View.VISIBLE);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case IN_PROGRESS:
|
|
||||||
progressBar.setVisibility(View.VISIBLE);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DONE:
|
|
||||||
doneIcon.setVisibility(View.VISIBLE);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ERROR:
|
|
||||||
errorIcon.setVisibility(View.VISIBLE);
|
|
||||||
error.setVisibility(View.VISIBLE);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FOLLOW_ERROR:
|
|
||||||
errorIcon.setVisibility(View.VISIBLE);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStates(UploadState[] states) {
|
|
||||||
this.states = states;
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public UploadStateAdapter.MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
|
||||||
|
|
||||||
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_upload_state, parent, false);
|
|
||||||
return new UploadStateAdapter.MyViewHolder(itemView);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(@NonNull UploadStateAdapter.MyViewHolder holder, int position) {
|
|
||||||
UploadState state = states[position];
|
|
||||||
|
|
||||||
holder.title.setText(state.getTitle());
|
|
||||||
holder.error.setText(state.getErrorMessage());
|
|
||||||
holder.setState(states[position].getCurrentState());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return states.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,157 +0,0 @@
|
||||||
package de.overview.wg.its.mispbump.auxiliary;
|
|
||||||
|
|
||||||
import android.util.Base64;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import javax.crypto.*;
|
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
import java.security.*;
|
|
||||||
import java.security.spec.InvalidKeySpecException;
|
|
||||||
import java.security.spec.X509EncodedKeySpec;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
public class AESSecurity {
|
|
||||||
|
|
||||||
private static final String TAG = "MISP_LOGGING";
|
|
||||||
|
|
||||||
private static final String ENCRYPT_ALGORITHM = "AES/CBC/PKCS5Padding";
|
|
||||||
private static final String KEY_PAIR_ALGORITHM = "EC";
|
|
||||||
private static final int KEY_SIZE = 521; // 224 | 256 | 384 | 521
|
|
||||||
private static final String KEY_AGREEMENT_ALGORITHM = "ECDH";
|
|
||||||
|
|
||||||
private static AESSecurity instance;
|
|
||||||
|
|
||||||
private PublicKey publickey;
|
|
||||||
private KeyAgreement keyAgreement;
|
|
||||||
|
|
||||||
private byte[] sharedSecret;
|
|
||||||
private IvParameterSpec ivParameterSpec;
|
|
||||||
|
|
||||||
private AESSecurity() {
|
|
||||||
initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
|
||||||
* Generates a public and a private key using an elliptic curve algorithm (256 bit)
|
|
||||||
* The private key is fed into the key agreement instance
|
|
||||||
*/
|
|
||||||
private void initialize() {
|
|
||||||
|
|
||||||
try {
|
|
||||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance(KEY_PAIR_ALGORITHM);
|
|
||||||
kpg.initialize(KEY_SIZE);
|
|
||||||
|
|
||||||
KeyPair kp = kpg.generateKeyPair();
|
|
||||||
publickey = kp.getPublic();
|
|
||||||
|
|
||||||
keyAgreement = KeyAgreement.getInstance(KEY_AGREEMENT_ALGORITHM);
|
|
||||||
keyAgreement.init(kp.getPrivate());
|
|
||||||
|
|
||||||
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
|
||||||
* Generates a shared secret with a given public key
|
|
||||||
* @param publickey
|
|
||||||
*/
|
|
||||||
public void setForeignPublicKey(PublicKey publickey) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
keyAgreement.doPhase(publickey, true);
|
|
||||||
|
|
||||||
byte[] tmpSharedSecret = keyAgreement.generateSecret();
|
|
||||||
|
|
||||||
sharedSecret = Arrays.copyOfRange(tmpSharedSecret, 0, 32);
|
|
||||||
|
|
||||||
byte[] inputVector = Arrays.copyOfRange(sharedSecret, 32, 48);
|
|
||||||
|
|
||||||
ivParameterSpec = new IvParameterSpec(inputVector);
|
|
||||||
|
|
||||||
} catch (InvalidKeyException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String encrypt(String data) {
|
|
||||||
try {
|
|
||||||
|
|
||||||
Key key = generateKey();
|
|
||||||
Cipher c = Cipher.getInstance(ENCRYPT_ALGORITHM);
|
|
||||||
|
|
||||||
try {
|
|
||||||
c.init(Cipher.ENCRYPT_MODE, key, ivParameterSpec);
|
|
||||||
} catch (InvalidAlgorithmParameterException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] encVal = c.doFinal(data.getBytes());
|
|
||||||
return Base64.encodeToString(encVal, 0);
|
|
||||||
|
|
||||||
} catch (BadPaddingException | InvalidKeyException | NoSuchPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String decrypt(String data) {
|
|
||||||
try {
|
|
||||||
Key key = generateKey();
|
|
||||||
|
|
||||||
Cipher c = Cipher.getInstance(ENCRYPT_ALGORITHM);
|
|
||||||
|
|
||||||
try {
|
|
||||||
c.init(Cipher.DECRYPT_MODE, key, ivParameterSpec);
|
|
||||||
} catch (InvalidAlgorithmParameterException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] decoded = Base64.decode(data, 0);
|
|
||||||
byte[] decValue = c.doFinal(decoded);
|
|
||||||
return new String(decValue);
|
|
||||||
} catch (BadPaddingException | InvalidKeyException | NoSuchPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PublicKey getPublicKey() {
|
|
||||||
return publickey;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Key generateKey() {
|
|
||||||
|
|
||||||
return new SecretKeySpec(sharedSecret, ENCRYPT_ALGORITHM);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String publicKeyToString(PublicKey key) {
|
|
||||||
return Base64.encodeToString(key.getEncoded(), Base64.DEFAULT);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PublicKey publicKeyFromString(String key) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
byte[] input = Base64.decode(key, Base64.DEFAULT);
|
|
||||||
return KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(input));
|
|
||||||
|
|
||||||
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AESSecurity getInstance() {
|
|
||||||
|
|
||||||
if(instance == null) {
|
|
||||||
instance = new AESSecurity();
|
|
||||||
}
|
|
||||||
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,142 +0,0 @@
|
||||||
package de.overview.wg.its.mispbump.auxiliary;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
import de.overview.wg.its.mispbump.model.Organisation;
|
|
||||||
import de.overview.wg.its.mispbump.model.SyncedPartner;
|
|
||||||
import de.overview.wg.its.mispbump.model.User;
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class PreferenceManager {
|
|
||||||
|
|
||||||
private static PreferenceManager instance;
|
|
||||||
|
|
||||||
private SharedPreferences userPreferences;
|
|
||||||
private SharedPreferences credentialPreferences;
|
|
||||||
private SharedPreferences syncedInstancesPreferences;
|
|
||||||
|
|
||||||
private static final String CREDENTIAL_PREFERENCE = "de.overview.wg.its.mispauth.credential_preference";
|
|
||||||
private static final String SAVED_INSTANCES_PREFERENCE = "de.overview.wg.its.mispauth.saved_instances_preference";
|
|
||||||
private static final String USER_PREFERENCE = "de.overview.wg.its.mispauth.user_preferences";
|
|
||||||
|
|
||||||
private static String PREF_KEY_SERVER_URL = "key_server_url";
|
|
||||||
private static String PREF_KEY_SERVER_API_KEY = "key_server_api_key";
|
|
||||||
private static String PREF_KEY_MY_ORGANISATION = "key_my_organisation";
|
|
||||||
private static String PREF_KEY_MY_USER = "key_my_user";
|
|
||||||
private static String PREF_KEY_SAVE_AUTHKEY_ENABLED = "key_save_authkey_enabled";
|
|
||||||
private static String PREF_KEY_SYNCED_ORGANISATIONS = "key_synced_organisations";
|
|
||||||
|
|
||||||
private PreferenceManager(Context context) {
|
|
||||||
credentialPreferences = context.getSharedPreferences(CREDENTIAL_PREFERENCE, Context.MODE_PRIVATE);
|
|
||||||
syncedInstancesPreferences = context.getSharedPreferences(SAVED_INSTANCES_PREFERENCE, Context.MODE_PRIVATE);
|
|
||||||
userPreferences = context.getSharedPreferences(USER_PREFERENCE, Context.MODE_PRIVATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<SyncedPartner> getSyncedPartnerList() {
|
|
||||||
String list = syncedInstancesPreferences.getString(PREF_KEY_SYNCED_ORGANISATIONS, "");
|
|
||||||
Type type = new TypeToken<List<SyncedPartner>>() {}.getType();
|
|
||||||
return new Gson().fromJson(list, type);
|
|
||||||
}
|
|
||||||
public void setSyncedPartnerList(List<SyncedPartner> syncedPartnerList) {
|
|
||||||
String json = new Gson().toJson(syncedPartnerList);
|
|
||||||
SharedPreferences.Editor editor = syncedInstancesPreferences.edit();
|
|
||||||
editor.putString(PREF_KEY_SYNCED_ORGANISATIONS, json);
|
|
||||||
editor.apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean saveAuthKeyEnabledExists() {
|
|
||||||
return userPreferences.contains(PREF_KEY_SAVE_AUTHKEY_ENABLED);
|
|
||||||
}
|
|
||||||
public boolean saveAuthKeyEnabled() {
|
|
||||||
return userPreferences.getBoolean(PREF_KEY_SAVE_AUTHKEY_ENABLED, false);
|
|
||||||
}
|
|
||||||
public void setSaveAuthKeyEnabled(boolean save) {
|
|
||||||
SharedPreferences.Editor editor = userPreferences.edit();
|
|
||||||
editor.putBoolean(PREF_KEY_SAVE_AUTHKEY_ENABLED, save);
|
|
||||||
editor.apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return own Organisation if available, else null
|
|
||||||
*/
|
|
||||||
public Organisation getMyOrganisation() {
|
|
||||||
try {
|
|
||||||
JSONObject jsonObject = new JSONObject(credentialPreferences.getString(PREF_KEY_MY_ORGANISATION, ""));
|
|
||||||
Organisation org = new Organisation();
|
|
||||||
org.fromJSON(jsonObject);
|
|
||||||
return org;
|
|
||||||
} catch (JSONException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
public void setMyOrganisation(Organisation org) {
|
|
||||||
SharedPreferences.Editor editor = credentialPreferences.edit();
|
|
||||||
editor.putString(PREF_KEY_MY_ORGANISATION, org.toJSON().toString());
|
|
||||||
editor.apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
public User getMyUser() {
|
|
||||||
try {
|
|
||||||
JSONObject jsonObject = new JSONObject(credentialPreferences.getString(PREF_KEY_MY_USER, ""));
|
|
||||||
User user = new User();
|
|
||||||
user.fromJSON(jsonObject);
|
|
||||||
return user;
|
|
||||||
} catch (JSONException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
public void setMyUser(User user) {
|
|
||||||
SharedPreferences.Editor editor = credentialPreferences.edit();
|
|
||||||
editor.putString(PREF_KEY_MY_USER, user.toJSON().toString());
|
|
||||||
editor.apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMyServerUrl() {
|
|
||||||
return credentialPreferences.getString(PREF_KEY_SERVER_URL, "");
|
|
||||||
}
|
|
||||||
public void setServerUrl(String serverUrl) {
|
|
||||||
SharedPreferences.Editor editor = credentialPreferences.edit();
|
|
||||||
editor.putString(PREF_KEY_SERVER_URL, serverUrl);
|
|
||||||
editor.apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMyServerAutomationKey() {
|
|
||||||
return credentialPreferences.getString(PREF_KEY_SERVER_API_KEY, "");
|
|
||||||
}
|
|
||||||
public void setAutomationKey(String apiKey) {
|
|
||||||
SharedPreferences.Editor editor = credentialPreferences.edit();
|
|
||||||
editor.putString(PREF_KEY_SERVER_API_KEY, apiKey);
|
|
||||||
editor.apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void clearUserPreferences() {
|
|
||||||
userPreferences.edit().clear().apply();
|
|
||||||
}
|
|
||||||
public void clearCredentialPreferences() {
|
|
||||||
credentialPreferences.edit().clear().apply();
|
|
||||||
}
|
|
||||||
public void clearSyncedInformationPreferences() {
|
|
||||||
syncedInstancesPreferences.edit().clear().apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static PreferenceManager Instance(Context context) {
|
|
||||||
if(instance == null) {
|
|
||||||
instance = new PreferenceManager(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
package de.overview.wg.its.mispbump.auxiliary;
|
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
public class RandomString {
|
|
||||||
|
|
||||||
@SuppressWarnings("SpellCheckingInspection")
|
|
||||||
private static final String upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
||||||
private static final String lower = upper.toLowerCase(Locale.ROOT);
|
|
||||||
private static final String digits = "0123456789";
|
|
||||||
private static final String alphaNum = upper + lower + digits;
|
|
||||||
|
|
||||||
private final Random random;
|
|
||||||
private final char[] symbols;
|
|
||||||
private final char[] buf;
|
|
||||||
|
|
||||||
private RandomString(int length, Random random, String symbols) {
|
|
||||||
|
|
||||||
if (length < 1) {
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
}
|
|
||||||
if (symbols.length() < 2) {
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.random = Objects.requireNonNull(random);
|
|
||||||
this.symbols = symbols.toCharArray();
|
|
||||||
this.buf = new char[length];
|
|
||||||
|
|
||||||
}
|
|
||||||
private RandomString(int length, Random random) {
|
|
||||||
this(length, random, alphaNum);
|
|
||||||
}
|
|
||||||
public RandomString(int length) {
|
|
||||||
this(length, new SecureRandom());
|
|
||||||
}
|
|
||||||
|
|
||||||
public String nextString() {
|
|
||||||
|
|
||||||
for (int idx = 0; idx < buf.length; ++idx) {
|
|
||||||
buf[idx] = symbols[random.nextInt(symbols.length)];
|
|
||||||
}
|
|
||||||
|
|
||||||
return new String(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
package de.overview.wg.its.mispbump.auxiliary;
|
|
||||||
|
|
||||||
import com.android.volley.*;
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
public class ReadableError {
|
|
||||||
|
|
||||||
public static String toReadable(VolleyError volleyError) {
|
|
||||||
|
|
||||||
if (volleyError.networkResponse != null) {
|
|
||||||
try {
|
|
||||||
JSONObject response = new JSONObject(new String(volleyError.networkResponse.data, StandardCharsets.UTF_8));
|
|
||||||
JSONObject error = response.getJSONObject("errors");
|
|
||||||
|
|
||||||
String name = response.getString("name");
|
|
||||||
String errorName = error.getJSONArray("name").get(0).toString();
|
|
||||||
|
|
||||||
if (!errorName.equals("")) {
|
|
||||||
return errorName;
|
|
||||||
} else if (!name.equals("")) {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (JSONException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (volleyError instanceof NoConnectionError) {
|
|
||||||
return "Connection failed";
|
|
||||||
} else if (volleyError instanceof TimeoutError) {
|
|
||||||
return "Connection timed out";
|
|
||||||
} else if (volleyError instanceof NetworkError) {
|
|
||||||
return "Network error";
|
|
||||||
} else if (volleyError instanceof AuthFailureError) {
|
|
||||||
return "Authentication failed";
|
|
||||||
} else if (volleyError instanceof ServerError) {
|
|
||||||
return "Server error";
|
|
||||||
} else if (volleyError instanceof ParseError) {
|
|
||||||
return "Parsing error";
|
|
||||||
}
|
|
||||||
|
|
||||||
return volleyError.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
package de.overview.wg.its.mispbump.auxiliary;
|
|
||||||
|
|
||||||
public class TempAuth {
|
|
||||||
|
|
||||||
public static String TMP_AUTH_KEY;
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,76 +0,0 @@
|
||||||
package de.overview.wg.its.mispbump.cam;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Copyright 2014 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.TextureView;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link TextureView} that can be adjusted to a specified aspect ratio.
|
|
||||||
*/
|
|
||||||
public class AutoFitTextureView extends TextureView {
|
|
||||||
|
|
||||||
private int mRatioWidth = 0;
|
|
||||||
private int mRatioHeight = 0;
|
|
||||||
|
|
||||||
public AutoFitTextureView(Context context) {
|
|
||||||
this(context, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AutoFitTextureView(Context context, AttributeSet attrs) {
|
|
||||||
this(context, attrs, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) {
|
|
||||||
super(context, attrs, defStyle);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
|
|
||||||
* calculated from the parameters. Note that the actual sizes of parameters don't matter, that
|
|
||||||
* is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
|
|
||||||
*
|
|
||||||
* @param width Relative horizontal size
|
|
||||||
* @param height Relative vertical size
|
|
||||||
*/
|
|
||||||
public void setAspectRatio(int width, int height) {
|
|
||||||
if (width < 0 || height < 0) {
|
|
||||||
throw new IllegalArgumentException("Size cannot be negative.");
|
|
||||||
}
|
|
||||||
mRatioWidth = width;
|
|
||||||
mRatioHeight = height;
|
|
||||||
requestLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
||||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
|
||||||
int width = MeasureSpec.getSize(widthMeasureSpec);
|
|
||||||
int height = MeasureSpec.getSize(heightMeasureSpec);
|
|
||||||
if (0 == mRatioWidth || 0 == mRatioHeight) {
|
|
||||||
setMeasuredDimension(width, height);
|
|
||||||
} else {
|
|
||||||
if (width < height * mRatioWidth / mRatioHeight) {
|
|
||||||
setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
|
|
||||||
} else {
|
|
||||||
setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,193 +0,0 @@
|
||||||
package de.overview.wg.its.mispbump.model;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
public class Organisation {
|
|
||||||
|
|
||||||
public static final String ROOT_KEY = "Organisation";
|
|
||||||
|
|
||||||
private static String ID_KEY = "id";
|
|
||||||
private static String NAME_KEY = "name";
|
|
||||||
private static String DATE_CREATED_KEY = "date_created";
|
|
||||||
private static String DATE_MODIFIED_KEY = "date_modified";
|
|
||||||
private static String TYPE_KEY = "type";
|
|
||||||
private static String NATIONALITY_KEY = "nationality";
|
|
||||||
private static String SECTOR_KEY = "sector";
|
|
||||||
private static String CONTACTS_KEY = "contacts";
|
|
||||||
private static String DESCRIPTION_KEY = "description";
|
|
||||||
private static String LOCAL_KEY = "local";
|
|
||||||
private static String UUID_KEY = "uuid";
|
|
||||||
private static String RESTRICTED_TO_DOMAIN_KEY = "restricted_to_domain";
|
|
||||||
private static String CREATED_BY_KEY = "created_by";
|
|
||||||
private static String USER_COUNT_KEY = "user_count";
|
|
||||||
|
|
||||||
private int id;
|
|
||||||
private String name;
|
|
||||||
private String dateCreated, dateModified;
|
|
||||||
private String type;
|
|
||||||
private String nationality;
|
|
||||||
private String sector;
|
|
||||||
private String contacts;
|
|
||||||
private String description;
|
|
||||||
private boolean local;
|
|
||||||
private String uuid;
|
|
||||||
private String restrictedToDomain;
|
|
||||||
private int createdBy;
|
|
||||||
private int userCount;
|
|
||||||
|
|
||||||
public Organisation() {}
|
|
||||||
|
|
||||||
public Organisation(JSONObject json) throws JSONException {
|
|
||||||
fromJSON(json);
|
|
||||||
}
|
|
||||||
public void fromJSON(JSONObject org) throws JSONException {
|
|
||||||
|
|
||||||
id = org.optInt(ID_KEY, -1);
|
|
||||||
dateCreated = org.optString(DATE_CREATED_KEY);
|
|
||||||
dateModified = org.optString(DATE_MODIFIED_KEY);
|
|
||||||
name = org.optString(NAME_KEY);
|
|
||||||
type = org.optString(TYPE_KEY);
|
|
||||||
nationality = org.optString(NATIONALITY_KEY);
|
|
||||||
sector = org.optString(SECTOR_KEY);
|
|
||||||
contacts = org.optString(CONTACTS_KEY);
|
|
||||||
description = org.optString(DESCRIPTION_KEY);
|
|
||||||
local = org.optBoolean(LOCAL_KEY, true);
|
|
||||||
uuid = org.optString(UUID_KEY);
|
|
||||||
restrictedToDomain = org.optString(RESTRICTED_TO_DOMAIN_KEY);
|
|
||||||
createdBy = org.optInt(CREATED_BY_KEY, -1);
|
|
||||||
userCount = org.optInt(USER_COUNT_KEY);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public JSONObject toJSON() {
|
|
||||||
return toJSON(false);
|
|
||||||
}
|
|
||||||
public JSONObject toJSON(boolean minimal) {
|
|
||||||
JSONObject org = new JSONObject();
|
|
||||||
|
|
||||||
try {
|
|
||||||
org.putOpt(NAME_KEY, name);
|
|
||||||
org.putOpt(DESCRIPTION_KEY, description);
|
|
||||||
org.putOpt(NATIONALITY_KEY, nationality);
|
|
||||||
org.putOpt(SECTOR_KEY, sector);
|
|
||||||
org.putOpt(USER_COUNT_KEY, userCount);
|
|
||||||
|
|
||||||
if (!minimal) {
|
|
||||||
org.putOpt(ID_KEY, id);
|
|
||||||
org.putOpt(UUID_KEY, uuid);
|
|
||||||
org.putOpt(TYPE_KEY, type);
|
|
||||||
org.putOpt(CONTACTS_KEY, contacts);
|
|
||||||
org.putOpt(DATE_CREATED_KEY, dateCreated);
|
|
||||||
org.putOpt(DATE_MODIFIED_KEY, dateModified);
|
|
||||||
org.putOpt(LOCAL_KEY, local);
|
|
||||||
org.putOpt(RESTRICTED_TO_DOMAIN_KEY, restrictedToDomain);
|
|
||||||
org.putOpt(CREATED_BY_KEY, createdBy);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (JSONException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
return org;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setName(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDescription() {
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
public void setDescription(String description) {
|
|
||||||
this.description = description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSector() {
|
|
||||||
return sector;
|
|
||||||
}
|
|
||||||
public void setSector(String sector) {
|
|
||||||
this.sector = sector;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getNationality() {
|
|
||||||
return nationality;
|
|
||||||
}
|
|
||||||
public void setNationality(String nationality) {
|
|
||||||
this.nationality = nationality;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
public void setId(int id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDateCreated() {
|
|
||||||
return dateCreated;
|
|
||||||
}
|
|
||||||
public void setDateCreated(String dateCreated) {
|
|
||||||
this.dateCreated = dateCreated;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDateModified() {
|
|
||||||
return dateModified;
|
|
||||||
}
|
|
||||||
public void setDateModified(String dateModified) {
|
|
||||||
this.dateModified = dateModified;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getType() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
public void setType(String type) {
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getContacts() {
|
|
||||||
return contacts;
|
|
||||||
}
|
|
||||||
public void setContacts(String contacts) {
|
|
||||||
this.contacts = contacts;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isLocal() {
|
|
||||||
return local;
|
|
||||||
}
|
|
||||||
public void setLocal(boolean local) {
|
|
||||||
this.local = local;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUuid() {
|
|
||||||
return uuid;
|
|
||||||
}
|
|
||||||
public void setUuid(String uuid) {
|
|
||||||
this.uuid = uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRestrictedToDomain() {
|
|
||||||
return restrictedToDomain;
|
|
||||||
}
|
|
||||||
public void setRestrictedToDomain(String restrictedToDomain) {
|
|
||||||
this.restrictedToDomain = restrictedToDomain;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCreatedBy() {
|
|
||||||
return createdBy;
|
|
||||||
}
|
|
||||||
public void setCreatedBy(int createdBy) {
|
|
||||||
this.createdBy = createdBy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getUserCount() {
|
|
||||||
return userCount;
|
|
||||||
}
|
|
||||||
public void setUserCount(int userCount) {
|
|
||||||
this.userCount = userCount;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
package de.overview.wg.its.mispbump.model;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
public class PublicKeyQr {
|
|
||||||
|
|
||||||
private static final String KEY_ORG = "org";
|
|
||||||
private static final String KEY_EMAIL = "email";
|
|
||||||
private static final String KEY_KEY = "key";
|
|
||||||
|
|
||||||
private String organisation, email, key;
|
|
||||||
|
|
||||||
public PublicKeyQr(JSONObject qr) throws JSONException {
|
|
||||||
organisation = qr.getString(KEY_ORG);
|
|
||||||
email = qr.getString(KEY_EMAIL);
|
|
||||||
key = qr.getString(KEY_KEY);
|
|
||||||
}
|
|
||||||
public PublicKeyQr(String qr) throws JSONException {
|
|
||||||
JSONObject json = new JSONObject(qr);
|
|
||||||
|
|
||||||
organisation = json.getString(KEY_ORG);
|
|
||||||
email = json.getString(KEY_EMAIL);
|
|
||||||
key = json.getString(KEY_KEY);
|
|
||||||
}
|
|
||||||
public PublicKeyQr(String organisation, String email, String key) {
|
|
||||||
this.organisation = organisation;
|
|
||||||
this.email = email;
|
|
||||||
this.key = key;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JSONObject toJSON() {
|
|
||||||
try {
|
|
||||||
JSONObject json = new JSONObject();
|
|
||||||
|
|
||||||
json.put(KEY_ORG, organisation);
|
|
||||||
json.put(KEY_EMAIL, email);
|
|
||||||
json.put(KEY_KEY, key);
|
|
||||||
|
|
||||||
return json;
|
|
||||||
|
|
||||||
} catch (JSONException e) {
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getOrganisation() {
|
|
||||||
return organisation;
|
|
||||||
}
|
|
||||||
public String getEmail() {
|
|
||||||
return email;
|
|
||||||
}
|
|
||||||
public String getKey() {
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,105 +0,0 @@
|
||||||
package de.overview.wg.its.mispbump.model;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
public class Server {
|
|
||||||
|
|
||||||
public static final String ROOT_KEY = "Server";
|
|
||||||
|
|
||||||
private static final String URL_KEY = "url";
|
|
||||||
private static final String NAME_KEY = "name";
|
|
||||||
private static final String REMOTE_ORG_ID_KEY = "remote_org_id";
|
|
||||||
private static final String AUTHKEY_KEY = "authkey";
|
|
||||||
private static final String PUSH_KEY = "push";
|
|
||||||
private static final String PULL_KEY = "pull";
|
|
||||||
|
|
||||||
private String url;
|
|
||||||
private String name;
|
|
||||||
private int remoteOrgId;
|
|
||||||
private String authkey;
|
|
||||||
private boolean push, pull;
|
|
||||||
|
|
||||||
public Server() { }
|
|
||||||
public Server(JSONObject json) throws JSONException {
|
|
||||||
fromJSON(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void fromJSON(JSONObject server) throws JSONException {
|
|
||||||
url = server.optString(URL_KEY);
|
|
||||||
name = server.optString(NAME_KEY);
|
|
||||||
remoteOrgId = server.optInt(REMOTE_ORG_ID_KEY, -1);
|
|
||||||
authkey = server.optString(AUTHKEY_KEY);
|
|
||||||
push = server.optBoolean(PUSH_KEY, false);
|
|
||||||
pull = server.optBoolean(PULL_KEY, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public JSONObject toJSON() {
|
|
||||||
return toJSON(false);
|
|
||||||
}
|
|
||||||
public JSONObject toJSON(boolean minimal) {
|
|
||||||
|
|
||||||
JSONObject jsonObject = new JSONObject();
|
|
||||||
|
|
||||||
try {
|
|
||||||
jsonObject.putOpt(URL_KEY, url);
|
|
||||||
jsonObject.putOpt(NAME_KEY, name);
|
|
||||||
jsonObject.putOpt(AUTHKEY_KEY, authkey);
|
|
||||||
|
|
||||||
if (!minimal) {
|
|
||||||
jsonObject.putOpt(REMOTE_ORG_ID_KEY, remoteOrgId);
|
|
||||||
jsonObject.putOpt(PUSH_KEY, push);
|
|
||||||
jsonObject.putOpt(PULL_KEY, pull);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (JSONException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
return jsonObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String getUrl() {
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
public void setUrl(String url) {
|
|
||||||
this.url = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
public void setName(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRemoteOrgId() {
|
|
||||||
return remoteOrgId;
|
|
||||||
}
|
|
||||||
public void setRemoteOrgId(int remoteOrgId) {
|
|
||||||
this.remoteOrgId = remoteOrgId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAuthkey() {
|
|
||||||
return authkey;
|
|
||||||
}
|
|
||||||
public void setAuthkey(String authkey) {
|
|
||||||
this.authkey = authkey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isPush() {
|
|
||||||
return push;
|
|
||||||
}
|
|
||||||
public void setPush(boolean push) {
|
|
||||||
this.push = push;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isPull() {
|
|
||||||
return pull;
|
|
||||||
}
|
|
||||||
public void setPull(boolean pull) {
|
|
||||||
this.pull = pull;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
package de.overview.wg.its.mispbump.model;
|
|
||||||
|
|
||||||
public class StringPair {
|
|
||||||
|
|
||||||
public String key, value;
|
|
||||||
|
|
||||||
public StringPair(String key, String value) {
|
|
||||||
this.key = key;
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
package de.overview.wg.its.mispbump.model;
|
|
||||||
|
|
||||||
import org.json.JSONArray;
|
|
||||||
import org.json.JSONException;
|
|
||||||
|
|
||||||
public class SyncInformationQr {
|
|
||||||
|
|
||||||
private Organisation organisation;
|
|
||||||
private Server server;
|
|
||||||
private User user;
|
|
||||||
|
|
||||||
public SyncInformationQr(Organisation organisation, Server server, User user) {
|
|
||||||
this.organisation = organisation;
|
|
||||||
this.server = server;
|
|
||||||
this.user = user;
|
|
||||||
}
|
|
||||||
public SyncInformationQr(String stringArray) throws JSONException {
|
|
||||||
fromJSON(new JSONArray(stringArray));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void fromJSON(JSONArray array) throws JSONException {
|
|
||||||
int length = array.length();
|
|
||||||
|
|
||||||
if (length == 3) {
|
|
||||||
organisation = new Organisation(array.getJSONObject(0));
|
|
||||||
server = new Server(array.getJSONObject(1));
|
|
||||||
user = new User(array.getJSONObject(2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public JSONArray toJSON() {
|
|
||||||
JSONArray array = new JSONArray();
|
|
||||||
|
|
||||||
array.put(organisation.toJSON(true));
|
|
||||||
array.put(server.toJSON(true));
|
|
||||||
array.put(user.toJSON(true));
|
|
||||||
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Organisation getOrganisation() {
|
|
||||||
return organisation;
|
|
||||||
}
|
|
||||||
public Server getServer() {
|
|
||||||
return server;
|
|
||||||
}
|
|
||||||
public User getUser() {
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
package de.overview.wg.its.mispbump.model;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
|
|
||||||
import java.sql.Timestamp;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
|
|
||||||
public class SyncedPartner {
|
|
||||||
|
|
||||||
@SuppressLint("SimpleDateFormat")
|
|
||||||
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy");
|
|
||||||
|
|
||||||
private String name;
|
|
||||||
private String url;
|
|
||||||
private String syncDate;
|
|
||||||
|
|
||||||
public SyncedPartner(String name, String url) {
|
|
||||||
this.name = name;
|
|
||||||
this.url = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void generateTimeStamp() {
|
|
||||||
syncDate = dateFormat.format(new Timestamp(System.currentTimeMillis()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// GETTER & SETTER
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
public void setName(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUrl() {
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
public void setUrl(String url) {
|
|
||||||
this.url = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSyncDate() {
|
|
||||||
return syncDate;
|
|
||||||
}
|
|
||||||
public void setSyncDate(String syncDate) {
|
|
||||||
this.syncDate = syncDate;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
package de.overview.wg.its.mispbump.model;
|
|
||||||
|
|
||||||
public class UploadState {
|
|
||||||
|
|
||||||
public enum State {
|
|
||||||
PENDING,
|
|
||||||
IN_PROGRESS,
|
|
||||||
DONE,
|
|
||||||
ERROR,
|
|
||||||
FOLLOW_ERROR
|
|
||||||
}
|
|
||||||
private State currentState = State.PENDING;
|
|
||||||
private String title, error;
|
|
||||||
|
|
||||||
|
|
||||||
public UploadState(String title) {
|
|
||||||
this.title = title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTitle() {
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTitle(String title) {
|
|
||||||
this.title = title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getErrorMessage() {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setError(String error) {
|
|
||||||
this.error = error;
|
|
||||||
this.currentState = State.ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDone() {
|
|
||||||
this.currentState = State.DONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInProgress() {
|
|
||||||
this.currentState = State.IN_PROGRESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPending() {
|
|
||||||
this.currentState = State.PENDING;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFollowError () {
|
|
||||||
this.currentState = State.FOLLOW_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
public State getCurrentState() {
|
|
||||||
return currentState;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,301 +0,0 @@
|
||||||
package de.overview.wg.its.mispbump.model;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
public class User {
|
|
||||||
|
|
||||||
// todo: must be configable? Roles can be edited on instance
|
|
||||||
public interface RoleId {
|
|
||||||
int ADMIN = 1;
|
|
||||||
int ORG_ADMIN = 2;
|
|
||||||
int USER = 3;
|
|
||||||
int PUBLISHER = 4;
|
|
||||||
int SYNC_USER = 5;
|
|
||||||
int READ_ONLY = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final String ROOT_KEY = "User";
|
|
||||||
|
|
||||||
private static String ID_KEY = "id";
|
|
||||||
private static String PASSWORD_KEY = "password";
|
|
||||||
private static String ORG_ID_KEY = "org_id";
|
|
||||||
private static String EMAIL_KEY = "email";
|
|
||||||
private static String AUTOALERT_KEY = "autoalert";
|
|
||||||
private static String AUTHKEY_KEY = "authkey";
|
|
||||||
private static String INVITED_BY_KEY = "invited_by";
|
|
||||||
private static String GPGKEY_KEY = "gpgkey";
|
|
||||||
private static String CERTIF_PUBLIC = "certif_public";
|
|
||||||
private static String NIDS_SID = "nids_sid";
|
|
||||||
private static String TERMS_ACCEPTED_KEY = "termsaccepted";
|
|
||||||
private static String NEWSREAD_KEY = "newsread";
|
|
||||||
private static String ROLE_ID_KEY = "role_id";
|
|
||||||
private static String CHANGE_PW_KEY = "change_pw";
|
|
||||||
private static String CONTACT_ALERT_KEY = "contactalert";
|
|
||||||
private static String DISABLED_KEY = "disabled";
|
|
||||||
private static String EXPIRATION_KEY = "expiration";
|
|
||||||
private static String CURRENT_LOGIN_KEY = "current_login";
|
|
||||||
private static String LAST_LOGIN_KEY = "last_login";
|
|
||||||
private static String FORCE_LOGOUT_KEY = "force_logout";
|
|
||||||
private static String DATE_CREATED_KEY = "date_created";
|
|
||||||
private static String DATE_MODIFIED_KEY = "date_modified";
|
|
||||||
|
|
||||||
private int id;
|
|
||||||
private String password;
|
|
||||||
private int orgId;
|
|
||||||
private String email;
|
|
||||||
private boolean autoAlert;
|
|
||||||
private String authkey;
|
|
||||||
private int invitedBy;
|
|
||||||
private String gpgKey;
|
|
||||||
private String certifPublic;
|
|
||||||
private int nidsSid;
|
|
||||||
private boolean termsAccepted;
|
|
||||||
private int newsRead; // Integer??
|
|
||||||
private int roleId;
|
|
||||||
private String changePw;
|
|
||||||
private boolean contactAlert;
|
|
||||||
private boolean disabled;
|
|
||||||
private String expiration;
|
|
||||||
private String currentLogin;
|
|
||||||
private String lastLogin;
|
|
||||||
private boolean forceLogout;
|
|
||||||
private String dateCreated;
|
|
||||||
private String dateModified;
|
|
||||||
|
|
||||||
public User() {}
|
|
||||||
|
|
||||||
public User(JSONObject user) throws JSONException {
|
|
||||||
fromJSON(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void fromJSON(JSONObject user) throws JSONException {
|
|
||||||
|
|
||||||
id = user.optInt(ID_KEY, -1);
|
|
||||||
password = user.optString(PASSWORD_KEY);
|
|
||||||
orgId = user.optInt(ORG_ID_KEY, -1);
|
|
||||||
email = user.optString(EMAIL_KEY);
|
|
||||||
autoAlert = user.optBoolean(AUTOALERT_KEY);
|
|
||||||
authkey = user.optString(AUTHKEY_KEY);
|
|
||||||
invitedBy = user.optInt(INVITED_BY_KEY, -1);
|
|
||||||
gpgKey = user.optString(GPGKEY_KEY);
|
|
||||||
certifPublic = user.optString(CERTIF_PUBLIC);
|
|
||||||
nidsSid = user.optInt(NIDS_SID);
|
|
||||||
termsAccepted = user.optBoolean(TERMS_ACCEPTED_KEY, false);
|
|
||||||
newsRead = user.optInt(NEWSREAD_KEY);
|
|
||||||
roleId = user.optInt(ROLE_ID_KEY, -1);
|
|
||||||
changePw = user.optString(CHANGE_PW_KEY);
|
|
||||||
contactAlert = user.optBoolean(CONTACT_ALERT_KEY, true);
|
|
||||||
disabled = user.optBoolean(DISABLED_KEY, false);
|
|
||||||
expiration = user.optString(EXPIRATION_KEY);
|
|
||||||
currentLogin = user.optString(CURRENT_LOGIN_KEY);
|
|
||||||
lastLogin = user.optString(LAST_LOGIN_KEY);
|
|
||||||
forceLogout = user.optBoolean(FORCE_LOGOUT_KEY);
|
|
||||||
dateCreated = user.optString(DATE_CREATED_KEY);
|
|
||||||
dateModified = user.optString(DATE_MODIFIED_KEY);
|
|
||||||
|
|
||||||
}
|
|
||||||
public JSONObject toJSON() {
|
|
||||||
return toJSON(false);
|
|
||||||
}
|
|
||||||
public JSONObject toJSON(boolean forSyncQR) {
|
|
||||||
JSONObject user = new JSONObject();
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
user.putOpt(EMAIL_KEY, email);
|
|
||||||
|
|
||||||
if (!forSyncQR) {
|
|
||||||
|
|
||||||
user.putOpt(ID_KEY, id);
|
|
||||||
user.putOpt(ORG_ID_KEY, orgId);
|
|
||||||
user.putOpt(AUTHKEY_KEY, authkey);
|
|
||||||
user.putOpt(ROLE_ID_KEY, roleId);
|
|
||||||
user.putOpt(PASSWORD_KEY, password);
|
|
||||||
user.putOpt(CHANGE_PW_KEY, changePw);
|
|
||||||
user.putOpt(TERMS_ACCEPTED_KEY, termsAccepted);
|
|
||||||
user.putOpt(CERTIF_PUBLIC, certifPublic);
|
|
||||||
user.putOpt(GPGKEY_KEY, gpgKey);
|
|
||||||
user.putOpt(AUTOALERT_KEY, autoAlert);
|
|
||||||
user.putOpt(INVITED_BY_KEY, invitedBy);
|
|
||||||
user.putOpt(NIDS_SID, nidsSid);
|
|
||||||
user.putOpt(NEWSREAD_KEY, newsRead);
|
|
||||||
user.putOpt(CONTACT_ALERT_KEY, contactAlert);
|
|
||||||
user.putOpt(DISABLED_KEY, disabled);
|
|
||||||
user.putOpt(EXPIRATION_KEY, expiration);
|
|
||||||
user.putOpt(CURRENT_LOGIN_KEY, currentLogin);
|
|
||||||
user.putOpt(LAST_LOGIN_KEY, lastLogin);
|
|
||||||
user.putOpt(FORCE_LOGOUT_KEY, forceLogout);
|
|
||||||
user.putOpt(DATE_CREATED_KEY, dateCreated);
|
|
||||||
user.putOpt(DATE_MODIFIED_KEY, dateModified);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (JSONException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearForStorage() {
|
|
||||||
setAuthkey("");
|
|
||||||
setGpgKey("");
|
|
||||||
setCertifPublic("");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
public void setId(int id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPassword() {
|
|
||||||
return password;
|
|
||||||
}
|
|
||||||
public void setPassword(String password) {
|
|
||||||
this.password = password;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getOrgId() {
|
|
||||||
return orgId;
|
|
||||||
}
|
|
||||||
public void setOrgId(int orgId) {
|
|
||||||
this.orgId = orgId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getEmail() {
|
|
||||||
return email;
|
|
||||||
}
|
|
||||||
public void setEmail(String email) {
|
|
||||||
this.email = email;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAutoAlert() {
|
|
||||||
return autoAlert;
|
|
||||||
}
|
|
||||||
public void setAutoAlert(boolean autoAlert) {
|
|
||||||
this.autoAlert = autoAlert;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAuthkey() {
|
|
||||||
return authkey;
|
|
||||||
}
|
|
||||||
public void setAuthkey(String authkey) {
|
|
||||||
this.authkey = authkey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getInvitedBy() {
|
|
||||||
return invitedBy;
|
|
||||||
}
|
|
||||||
public void setInvitedBy(int invitedBy) {
|
|
||||||
this.invitedBy = invitedBy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getGpgKey() {
|
|
||||||
return gpgKey;
|
|
||||||
}
|
|
||||||
public void setGpgKey(String gpgKey) {
|
|
||||||
this.gpgKey = gpgKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCertifPublic() {
|
|
||||||
return certifPublic;
|
|
||||||
}
|
|
||||||
public void setCertifPublic(String certifPublic) {
|
|
||||||
this.certifPublic = certifPublic;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getNidsSid() {
|
|
||||||
return nidsSid;
|
|
||||||
}
|
|
||||||
public void setNidsSid(int nidsSid) {
|
|
||||||
this.nidsSid = nidsSid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isTermsAccepted() {
|
|
||||||
return termsAccepted;
|
|
||||||
}
|
|
||||||
public void setTermsAccepted(boolean termsAccepted) {
|
|
||||||
this.termsAccepted = termsAccepted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getNewsRead() {
|
|
||||||
return newsRead;
|
|
||||||
}
|
|
||||||
public void setNewsRead(int newsRead) {
|
|
||||||
this.newsRead = newsRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRoleId() {
|
|
||||||
return roleId;
|
|
||||||
}
|
|
||||||
public void setRoleId(int roleId) {
|
|
||||||
this.roleId = roleId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getChangePw() {
|
|
||||||
return changePw;
|
|
||||||
}
|
|
||||||
public void setChangePw(String changePw) {
|
|
||||||
this.changePw = changePw;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isContactAlert() {
|
|
||||||
return contactAlert;
|
|
||||||
}
|
|
||||||
public void setContactAlert(boolean contactAlert) {
|
|
||||||
this.contactAlert = contactAlert;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDisabled() {
|
|
||||||
return disabled;
|
|
||||||
}
|
|
||||||
public void setDisabled(boolean disabled) {
|
|
||||||
this.disabled = disabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getExpiration() {
|
|
||||||
return expiration;
|
|
||||||
}
|
|
||||||
public void setExpiration(String expiration) {
|
|
||||||
this.expiration = expiration;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCurrentLogin() {
|
|
||||||
return currentLogin;
|
|
||||||
}
|
|
||||||
public void setCurrentLogin(String currentLogin) {
|
|
||||||
this.currentLogin = currentLogin;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLastLogin() {
|
|
||||||
return lastLogin;
|
|
||||||
}
|
|
||||||
public void setLastLogin(String lastLogin) {
|
|
||||||
this.lastLogin = lastLogin;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isForceLogout() {
|
|
||||||
return forceLogout;
|
|
||||||
}
|
|
||||||
public void setForceLogout(boolean forceLogout) {
|
|
||||||
this.forceLogout = forceLogout;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDateCreated() {
|
|
||||||
return dateCreated;
|
|
||||||
}
|
|
||||||
public void setDateCreated(String dateCreated) {
|
|
||||||
this.dateCreated = dateCreated;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDateModified() {
|
|
||||||
return dateModified;
|
|
||||||
}
|
|
||||||
public void setDateModified(String dateModified) {
|
|
||||||
this.dateModified = dateModified;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
package de.overview.wg.its.mispbump.network;
|
|
||||||
|
|
||||||
import com.android.volley.NetworkResponse;
|
|
||||||
import com.android.volley.ParseError;
|
|
||||||
import com.android.volley.Response;
|
|
||||||
import com.android.volley.toolbox.HttpHeaderParser;
|
|
||||||
import com.android.volley.toolbox.JsonRequest;
|
|
||||||
import org.json.JSONArray;
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
|
|
||||||
public class JsonArrayRequestWithJsonObject extends JsonRequest<JSONArray> {
|
|
||||||
/**
|
|
||||||
* Creates a new request.
|
|
||||||
* @param method the HTTP method to use
|
|
||||||
* @param url URL to fetch the JSON from
|
|
||||||
* @param jsonRequest A {@link JSONObject} to post with the request. Null is allowed and
|
|
||||||
* indicates no parameters will be posted along with request.
|
|
||||||
* @param listener Listener to receive the JSON response
|
|
||||||
* @param errorListener Error listener, or null to ignore errors.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public JsonArrayRequestWithJsonObject(int method, String url, JSONObject jsonRequest, Response.Listener<JSONArray> listener, Response.ErrorListener errorListener) {
|
|
||||||
super(method, url, (jsonRequest == null) ? null : jsonRequest.toString(), listener, errorListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Response<JSONArray> parseNetworkResponse(NetworkResponse response) {
|
|
||||||
try {
|
|
||||||
|
|
||||||
String jsonString = new String(response.data, HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET));
|
|
||||||
return Response.success(new JSONArray(jsonString), HttpHeaderParser.parseCacheHeaders(response));
|
|
||||||
|
|
||||||
} catch (UnsupportedEncodingException | JSONException e) {
|
|
||||||
|
|
||||||
return Response.error(new ParseError(e));
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,432 +0,0 @@
|
||||||
package de.overview.wg.its.mispbump.network;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import com.android.volley.Request;
|
|
||||||
import com.android.volley.RequestQueue;
|
|
||||||
import com.android.volley.Response;
|
|
||||||
import com.android.volley.VolleyError;
|
|
||||||
import com.android.volley.toolbox.JsonObjectRequest;
|
|
||||||
import com.android.volley.toolbox.Volley;
|
|
||||||
import de.overview.wg.its.mispbump.auxiliary.PreferenceManager;
|
|
||||||
import de.overview.wg.its.mispbump.model.Organisation;
|
|
||||||
import de.overview.wg.its.mispbump.model.Server;
|
|
||||||
import de.overview.wg.its.mispbump.model.User;
|
|
||||||
import org.json.JSONArray;
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON based API to communicate with MISP-Instances
|
|
||||||
*/
|
|
||||||
public class MispRequest {
|
|
||||||
|
|
||||||
private static MispRequest instance;
|
|
||||||
|
|
||||||
private RequestQueue requestQueue;
|
|
||||||
private PreferenceManager preferenceManager;
|
|
||||||
private String serverUrl, automationKey;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param context for Volley Request Q and PreferenceManager
|
|
||||||
*/
|
|
||||||
private MispRequest(Context context, boolean loadSavedCredentials) {
|
|
||||||
requestQueue = Volley.newRequestQueue(context);
|
|
||||||
|
|
||||||
if (loadSavedCredentials) {
|
|
||||||
preferenceManager = PreferenceManager.Instance(context);
|
|
||||||
loadSavedCredentials();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadSavedCredentials() {
|
|
||||||
serverUrl = preferenceManager.getMyServerUrl();
|
|
||||||
automationKey = preferenceManager.getMyServerAutomationKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testConnection(final ConnectionCallback callback) {
|
|
||||||
Response.Listener<JSONObject> listener = new Response.Listener<JSONObject>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(JSONObject response) {
|
|
||||||
callback.onResult(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Response.ErrorListener errorListener = new Response.ErrorListener() {
|
|
||||||
@Override
|
|
||||||
public void onErrorResponse(VolleyError error) {
|
|
||||||
callback.onResult(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Request r = objectRequest(Request.Method.GET,
|
|
||||||
serverUrl + "/servers/getPyMISPVersion.json",
|
|
||||||
null,
|
|
||||||
listener,
|
|
||||||
errorListener);
|
|
||||||
|
|
||||||
requestQueue.add(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void getOrganisations(final OrganisationsCallback callback) {
|
|
||||||
Response.Listener<JSONArray> listener = new Response.Listener<JSONArray>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(JSONArray response) {
|
|
||||||
|
|
||||||
JSONArray resultArray = new JSONArray();
|
|
||||||
|
|
||||||
int orgCount = response.length();
|
|
||||||
|
|
||||||
for (int i = 0; i < orgCount; i++) {
|
|
||||||
try {
|
|
||||||
resultArray.put(response.getJSONObject(i).getJSONObject("Organisation"));
|
|
||||||
} catch (JSONException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
callback.onResult(resultArray);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Response.ErrorListener errorListener = new Response.ErrorListener() {
|
|
||||||
@Override
|
|
||||||
public void onErrorResponse(VolleyError error) {
|
|
||||||
callback.onError(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Request r = arrayRequestWithJsonObject(
|
|
||||||
Request.Method.GET,
|
|
||||||
serverUrl + "/organisations/index",
|
|
||||||
null,
|
|
||||||
listener,
|
|
||||||
errorListener);
|
|
||||||
|
|
||||||
requestQueue.add(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param orgId organisation ID on the MISP-Instance
|
|
||||||
* @param callback returns a single Organisation-JSON
|
|
||||||
*/
|
|
||||||
public void getOrganisation(int orgId, final OrganisationCallback callback) {
|
|
||||||
|
|
||||||
Response.Listener<JSONObject> listener = new Response.Listener<JSONObject>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(JSONObject response) {
|
|
||||||
try {
|
|
||||||
callback.onResult(response.getJSONObject(Organisation.ROOT_KEY));
|
|
||||||
} catch (JSONException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Response.ErrorListener errorListener = new Response.ErrorListener() {
|
|
||||||
@Override
|
|
||||||
public void onErrorResponse(VolleyError error) {
|
|
||||||
callback.onError(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
Request r = objectRequest(Request.Method.GET,
|
|
||||||
serverUrl + "/organisations/view/" + orgId,
|
|
||||||
null,
|
|
||||||
listener,
|
|
||||||
errorListener);
|
|
||||||
|
|
||||||
requestQueue.add(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Typically used to get the organisation linked with this user
|
|
||||||
*
|
|
||||||
* @param callback return user associated with this API-Key
|
|
||||||
*/
|
|
||||||
public void getMyUser(final UserCallback callback) {
|
|
||||||
|
|
||||||
Response.Listener<JSONObject> listener = new Response.Listener<JSONObject>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(JSONObject response) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
callback.onResult(response.getJSONObject(User.ROOT_KEY));
|
|
||||||
return;
|
|
||||||
} catch (JSONException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
callback.onResult(response);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Response.ErrorListener errorListener = new Response.ErrorListener() {
|
|
||||||
@Override
|
|
||||||
public void onErrorResponse(VolleyError error) {
|
|
||||||
callback.onError(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (serverUrl.isEmpty() || automationKey.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Request r = objectRequest(
|
|
||||||
Request.Method.GET,
|
|
||||||
serverUrl + "/users/view/me",
|
|
||||||
null,
|
|
||||||
listener,
|
|
||||||
errorListener);
|
|
||||||
|
|
||||||
requestQueue.add(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param organisation The organisation that will be added
|
|
||||||
* @param callback returns complete organisation JSON
|
|
||||||
*/
|
|
||||||
public void addOrganisation(Organisation organisation, final OrganisationCallback callback) {
|
|
||||||
Response.Listener<JSONObject> listener = new Response.Listener<JSONObject>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(JSONObject response) {
|
|
||||||
try {
|
|
||||||
callback.onResult(response.getJSONObject(Organisation.ROOT_KEY));
|
|
||||||
} catch (JSONException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Response.ErrorListener errorListener = new Response.ErrorListener() {
|
|
||||||
@Override
|
|
||||||
public void onErrorResponse(VolleyError error) {
|
|
||||||
callback.onError(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Request r = objectRequest(
|
|
||||||
Request.Method.POST,
|
|
||||||
serverUrl + "/admin/organisations/add",
|
|
||||||
organisation.toJSON(),
|
|
||||||
listener,
|
|
||||||
errorListener
|
|
||||||
);
|
|
||||||
|
|
||||||
requestQueue.add(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeOrganisation(int organisationID, final DeleteCallback callback) {
|
|
||||||
Response.Listener<JSONObject> listener = new Response.Listener<JSONObject>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(JSONObject response) {
|
|
||||||
callback.onSuccess();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Response.ErrorListener errorListener = new Response.ErrorListener() {
|
|
||||||
@Override
|
|
||||||
public void onErrorResponse(VolleyError volleyError) {
|
|
||||||
callback.onError(volleyError);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Request r = objectRequest(
|
|
||||||
Request.Method.POST,
|
|
||||||
serverUrl + "/admin/organisations/delete/" + organisationID,
|
|
||||||
null,
|
|
||||||
listener,
|
|
||||||
errorListener
|
|
||||||
);
|
|
||||||
|
|
||||||
requestQueue.add(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addUser(User user, final UserCallback callback) {
|
|
||||||
Response.Listener<JSONObject> listener = new Response.Listener<JSONObject>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(JSONObject response) {
|
|
||||||
try {
|
|
||||||
callback.onResult(response.getJSONObject(User.ROOT_KEY));
|
|
||||||
return;
|
|
||||||
} catch (JSONException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
callback.onResult(response);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Response.ErrorListener errorListener = new Response.ErrorListener() {
|
|
||||||
@Override
|
|
||||||
public void onErrorResponse(VolleyError error) {
|
|
||||||
callback.onError(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Request r = objectRequest(
|
|
||||||
Request.Method.POST,
|
|
||||||
serverUrl + "/admin/users/add",
|
|
||||||
user.toJSON(),
|
|
||||||
listener,
|
|
||||||
errorListener
|
|
||||||
);
|
|
||||||
|
|
||||||
requestQueue.add(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeUser(int userID, final DeleteCallback callback) {
|
|
||||||
|
|
||||||
Response.Listener<JSONObject> listener = new Response.Listener<JSONObject>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(JSONObject response) {
|
|
||||||
callback.onSuccess();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Response.ErrorListener errorListener = new Response.ErrorListener() {
|
|
||||||
@Override
|
|
||||||
public void onErrorResponse(VolleyError error) {
|
|
||||||
callback.onError(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Request r = objectRequest(
|
|
||||||
Request.Method.POST,
|
|
||||||
serverUrl + "/admin/users/delete/" + userID,
|
|
||||||
null,
|
|
||||||
listener,
|
|
||||||
errorListener
|
|
||||||
);
|
|
||||||
|
|
||||||
requestQueue.add(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addServer(Server server, final ServerCallback callback) {
|
|
||||||
Response.Listener<JSONObject> listener = new Response.Listener<JSONObject>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(JSONObject response) {
|
|
||||||
try {
|
|
||||||
callback.onResult(response.getJSONObject(Server.ROOT_KEY));
|
|
||||||
return;
|
|
||||||
} catch (JSONException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
callback.onResult(response);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Response.ErrorListener errorListener = new Response.ErrorListener() {
|
|
||||||
@Override
|
|
||||||
public void onErrorResponse(VolleyError error) {
|
|
||||||
callback.onError(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Request r = objectRequest(
|
|
||||||
Request.Method.POST,
|
|
||||||
serverUrl + "/servers/add",
|
|
||||||
server.toJSON(),
|
|
||||||
listener,
|
|
||||||
errorListener
|
|
||||||
);
|
|
||||||
|
|
||||||
requestQueue.add(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeServer(Server server, final DeleteCallback callback) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private JsonArrayRequestWithJsonObject arrayRequestWithJsonObject(int method, String url,
|
|
||||||
@Nullable JSONObject body,
|
|
||||||
Response.Listener<JSONArray> listener,
|
|
||||||
Response.ErrorListener errorListener) {
|
|
||||||
|
|
||||||
return new JsonArrayRequestWithJsonObject(method, url, body, listener, errorListener) {
|
|
||||||
@Override
|
|
||||||
public Map<String, String> getHeaders() {
|
|
||||||
Map<String, String> params = new HashMap<>();
|
|
||||||
|
|
||||||
params.put("Authorization", automationKey);
|
|
||||||
params.put("Accept", "application/json");
|
|
||||||
params.put("Content-Type", "application/json; utf-8");
|
|
||||||
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private JsonObjectRequest objectRequest(int method, String url,
|
|
||||||
@Nullable JSONObject body,
|
|
||||||
Response.Listener<JSONObject> listener,
|
|
||||||
Response.ErrorListener errorListener) {
|
|
||||||
|
|
||||||
return new JsonObjectRequest(method, url, body, listener, errorListener) {
|
|
||||||
@Override
|
|
||||||
public Map<String, String> getHeaders() {
|
|
||||||
Map<String, String> params = new HashMap<>();
|
|
||||||
|
|
||||||
params.put("Authorization", automationKey);
|
|
||||||
params.put("Accept", "application/json");
|
|
||||||
params.put("Content-Type", "application/json; utf-8");
|
|
||||||
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setServerCredentials(String serverUrl, String automationKey) {
|
|
||||||
this.serverUrl = serverUrl;
|
|
||||||
this.automationKey = automationKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MispRequest Instance(Context context, boolean loadSavedCredentials) {
|
|
||||||
|
|
||||||
if (instance == null) {
|
|
||||||
instance = new MispRequest(context, loadSavedCredentials);
|
|
||||||
}
|
|
||||||
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public interface DeleteCallback {
|
|
||||||
void onSuccess();
|
|
||||||
|
|
||||||
void onError(VolleyError volleyError);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface ConnectionCallback {
|
|
||||||
void onResult(boolean connected);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface OrganisationsCallback {
|
|
||||||
void onResult(JSONArray organisations);
|
|
||||||
|
|
||||||
void onError(VolleyError volleyError);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface OrganisationCallback {
|
|
||||||
void onResult(JSONObject organisationInformation);
|
|
||||||
|
|
||||||
void onError(VolleyError volleyError);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface UserCallback {
|
|
||||||
void onResult(JSONObject userInformation);
|
|
||||||
|
|
||||||
void onError(VolleyError volleyError);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface ServerCallback {
|
|
||||||
void onResult(JSONObject server);
|
|
||||||
|
|
||||||
void onError(VolleyError volleyError);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,160 @@
|
||||||
|
package lu.circl.mispbump.activities;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import lu.circl.mispbump.R;
|
||||||
|
import lu.circl.mispbump.adapters.SyncAdapter;
|
||||||
|
import lu.circl.mispbump.auxiliary.DialogManager;
|
||||||
|
import lu.circl.mispbump.auxiliary.PreferenceManager;
|
||||||
|
import lu.circl.mispbump.interfaces.IOnItemClickListener;
|
||||||
|
import lu.circl.mispbump.models.UploadInformation;
|
||||||
|
|
||||||
|
public class HomeActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
public static final String TAG = "Home";
|
||||||
|
|
||||||
|
private CoordinatorLayout layout;
|
||||||
|
private RecyclerView recyclerView;
|
||||||
|
|
||||||
|
private PreferenceManager preferenceManager;
|
||||||
|
|
||||||
|
private View.OnClickListener onFabClicked = new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
Intent sync = new Intent(HomeActivity.this, SyncActivity.class);
|
||||||
|
startActivity(sync);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_home);
|
||||||
|
|
||||||
|
preferenceManager = PreferenceManager.getInstance(this);
|
||||||
|
|
||||||
|
initializeViews();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
initializeRecyclerView();
|
||||||
|
refreshSyncInformation();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
getMenuInflater().inflate(R.menu.main_menu, menu);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
if (item.getItemId() == R.id.menu_settings) {
|
||||||
|
startActivity(new Intent(HomeActivity.this, PreferenceActivity.class));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.getItemId() == R.id.menu_profile) {
|
||||||
|
startActivity(new Intent(HomeActivity.this, ProfileActivity.class));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// invoke superclass to handle unrecognized item (eg. homeAsUp)
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeViews() {
|
||||||
|
layout = findViewById(R.id.rootLayout);
|
||||||
|
|
||||||
|
// populate Toolbar (Actionbar)
|
||||||
|
Toolbar myToolbar = findViewById(R.id.toolbar);
|
||||||
|
setSupportActionBar(myToolbar);
|
||||||
|
|
||||||
|
ActionBar ab = getSupportActionBar();
|
||||||
|
if (ab != null) {
|
||||||
|
ab.setDisplayHomeAsUpEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
recyclerView = findViewById(R.id.recyclerView);
|
||||||
|
recyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||||
|
|
||||||
|
FloatingActionButton sync_fab = findViewById(R.id.home_fab);
|
||||||
|
sync_fab.setOnClickListener(onFabClicked);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshSyncInformation () {
|
||||||
|
List<UploadInformation> uploadInformationList = preferenceManager.getUploadInformation();
|
||||||
|
TextView empty = findViewById(R.id.emtpy);
|
||||||
|
|
||||||
|
// no sync information available
|
||||||
|
if (uploadInformationList == null) {
|
||||||
|
empty.setVisibility(View.VISIBLE);
|
||||||
|
recyclerView.setVisibility(View.GONE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sync information available
|
||||||
|
empty.setVisibility(View.GONE);
|
||||||
|
recyclerView.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
SyncAdapter adapter = (SyncAdapter) recyclerView.getAdapter();
|
||||||
|
assert adapter != null;
|
||||||
|
adapter.setUploadInformationList(uploadInformationList);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeRecyclerView() {
|
||||||
|
SyncAdapter syncAdapter = new SyncAdapter(HomeActivity.this);
|
||||||
|
syncAdapter.setOnDeleteClickListener(new IOnItemClickListener<UploadInformation>() {
|
||||||
|
@Override
|
||||||
|
public void onItemClick(final UploadInformation clickedObject) {
|
||||||
|
DialogManager.deleteSyncInformationDialog(HomeActivity.this, new DialogManager.IDialogFeedback() {
|
||||||
|
@Override
|
||||||
|
public void positive() {
|
||||||
|
boolean status = preferenceManager.removeUploadInformation(clickedObject.getId());
|
||||||
|
|
||||||
|
if (status) {
|
||||||
|
Snackbar.make(layout, "Successfully deleted sync information", Snackbar.LENGTH_LONG).show();
|
||||||
|
refreshSyncInformation();
|
||||||
|
} else {
|
||||||
|
Snackbar.make(layout, "Failed to delete sync information", Snackbar.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void negative() { }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
syncAdapter.setOnRetryClickListener(new IOnItemClickListener<UploadInformation>() {
|
||||||
|
@Override
|
||||||
|
public void onItemClick(UploadInformation clickedObject) {
|
||||||
|
Intent upload = new Intent(HomeActivity.this, UploadActivity.class);
|
||||||
|
upload.putExtra(UploadActivity.EXTRA_UPLOAD_INFO, new Gson().toJson(clickedObject));
|
||||||
|
startActivity(upload);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
recyclerView.setAdapter(syncAdapter);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,201 @@
|
||||||
|
package lu.circl.mispbump.activities;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
|
import com.google.android.material.textfield.TextInputLayout;
|
||||||
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.webkit.WebSettings;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import lu.circl.mispbump.R;
|
||||||
|
import lu.circl.mispbump.auxiliary.DialogManager;
|
||||||
|
import lu.circl.mispbump.auxiliary.PreferenceManager;
|
||||||
|
import lu.circl.mispbump.restful_client.MispRestClient;
|
||||||
|
import lu.circl.mispbump.restful_client.Organisation;
|
||||||
|
import lu.circl.mispbump.restful_client.User;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This activity is shown when the current device has no misp user associated with it.
|
||||||
|
* Takes care of downloading all information necessary for a sync with other misp instances.
|
||||||
|
*/
|
||||||
|
public class LoginActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
private PreferenceManager preferenceManager;
|
||||||
|
private ConstraintLayout constraintLayout;
|
||||||
|
private TextInputLayout serverAutomationKey;
|
||||||
|
private TextInputLayout serverUrl;
|
||||||
|
private ProgressBar progressBar;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_login);
|
||||||
|
initializeViews();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
getMenuInflater().inflate(R.menu.menu_login, menu);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
if (item.getItemId() == R.id.menu_login_help) {
|
||||||
|
DialogManager.loginHelpDialog(LoginActivity.this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// invoke superclass to handle unrecognized item (eg. homeAsUp)
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeViews() {
|
||||||
|
// populate Toolbar (Actionbar)
|
||||||
|
Toolbar myToolbar = findViewById(R.id.appbar);
|
||||||
|
setSupportActionBar(myToolbar);
|
||||||
|
|
||||||
|
ActionBar ab = getSupportActionBar();
|
||||||
|
if (ab != null) {
|
||||||
|
ab.setDisplayHomeAsUpEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
constraintLayout = findViewById(R.id.rootLayout);
|
||||||
|
|
||||||
|
serverUrl = findViewById(R.id.login_server_url);
|
||||||
|
serverAutomationKey = findViewById(R.id.login_automation_key);
|
||||||
|
Button downloadInfoButton = findViewById(R.id.login_download_button);
|
||||||
|
downloadInfoButton.setOnClickListener(onClickDownload);
|
||||||
|
|
||||||
|
progressBar = findViewById(R.id.login_progressbar);
|
||||||
|
|
||||||
|
preferenceManager = PreferenceManager.getInstance(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is called when the user clicks on the login button.
|
||||||
|
*/
|
||||||
|
private View.OnClickListener onClickDownload = new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
|
||||||
|
String url = Objects.requireNonNull(serverUrl.getEditText()).getText().toString();
|
||||||
|
String authkey = Objects.requireNonNull(serverAutomationKey.getEditText()).getText().toString();
|
||||||
|
|
||||||
|
boolean error = false;
|
||||||
|
|
||||||
|
serverUrl.setError(null);
|
||||||
|
serverAutomationKey.setError(null);
|
||||||
|
|
||||||
|
if (!isValidUrl(url)) {
|
||||||
|
error = true;
|
||||||
|
serverUrl.setError("Invalid Server URL");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isValidAutomationKey(authkey)) {
|
||||||
|
error = true;
|
||||||
|
serverAutomationKey.setError("Invalid automation key");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// save authkey
|
||||||
|
preferenceManager.setAutomationKey(authkey);
|
||||||
|
|
||||||
|
// save url
|
||||||
|
preferenceManager.setServerUrl(url);
|
||||||
|
|
||||||
|
// instance of MispRestClient with given URL
|
||||||
|
final MispRestClient mispRestClient = new MispRestClient(getApplicationContext());
|
||||||
|
|
||||||
|
// display progress bar
|
||||||
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
// get my user information and the organisation associated with my user
|
||||||
|
mispRestClient.isAvailable(new MispRestClient.AvailableCallback() {
|
||||||
|
@Override
|
||||||
|
public void available() {
|
||||||
|
mispRestClient.getMyUser(new MispRestClient.UserCallback() {
|
||||||
|
@Override
|
||||||
|
public void success(final User user) {
|
||||||
|
preferenceManager.setUserInfo(user);
|
||||||
|
mispRestClient.getOrganisation(user.org_id, new MispRestClient.OrganisationCallback() {
|
||||||
|
@Override
|
||||||
|
public void success(Organisation organisation) {
|
||||||
|
preferenceManager.setUserOrgInfo(organisation);
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
Intent home = new Intent(getApplicationContext(), HomeActivity.class);
|
||||||
|
startActivity(home);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void failure(String error) {
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
Snackbar.make(constraintLayout, error, Snackbar.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void failure(String error) {
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
Snackbar.make(constraintLayout, error, Snackbar.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unavailable(String error) {
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
Snackbar sb = Snackbar.make(constraintLayout, error, Snackbar.LENGTH_LONG);
|
||||||
|
sb.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if url is valid.
|
||||||
|
*
|
||||||
|
* @param url url to check
|
||||||
|
* @return true or false
|
||||||
|
*/
|
||||||
|
private boolean isValidUrl(String url) {
|
||||||
|
Uri uri = Uri.parse(url);
|
||||||
|
|
||||||
|
if (uri == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return uri.getScheme() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if automation key is valid.
|
||||||
|
*
|
||||||
|
* @param automationKey the key to check
|
||||||
|
* @return true or false
|
||||||
|
*/
|
||||||
|
private boolean isValidAutomationKey(String automationKey) {
|
||||||
|
return !TextUtils.isEmpty(automationKey);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package lu.circl.mispbump.activities;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Button;
|
||||||
|
|
||||||
|
import lu.circl.mispbump.R;
|
||||||
|
import lu.circl.mispbump.auxiliary.PreferenceManager;
|
||||||
|
|
||||||
|
public class PreferenceActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
private PreferenceManager preferenceManager;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_preference);
|
||||||
|
|
||||||
|
preferenceManager = PreferenceManager.getInstance(PreferenceActivity.this);
|
||||||
|
|
||||||
|
initializeViews();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeViews() {
|
||||||
|
Button deleteSyncs = findViewById(R.id.deleteSyncs);
|
||||||
|
deleteSyncs.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
preferenceManager.clearUploadInformation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,190 @@
|
||||||
|
package lu.circl.mispbump.activities;
|
||||||
|
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.graphics.Shader;
|
||||||
|
import android.graphics.drawable.AnimatedVectorDrawable;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import lu.circl.mispbump.R;
|
||||||
|
import lu.circl.mispbump.auxiliary.PreferenceManager;
|
||||||
|
import lu.circl.mispbump.auxiliary.TileDrawable;
|
||||||
|
import lu.circl.mispbump.custom_views.MaterialPreferenceText;
|
||||||
|
import lu.circl.mispbump.restful_client.MispRestClient;
|
||||||
|
import lu.circl.mispbump.restful_client.Organisation;
|
||||||
|
import lu.circl.mispbump.restful_client.User;
|
||||||
|
import lu.circl.mispbump.security.KeyStoreWrapper;
|
||||||
|
|
||||||
|
public class ProfileActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
private CoordinatorLayout rootLayout;
|
||||||
|
private MispRestClient mispRestClient;
|
||||||
|
private PreferenceManager preferenceManager;
|
||||||
|
|
||||||
|
private FloatingActionButton fab;
|
||||||
|
private AnimatedVectorDrawable fabLoadingDrawable;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_profile);
|
||||||
|
|
||||||
|
mispRestClient = new MispRestClient(this);
|
||||||
|
preferenceManager = PreferenceManager.getInstance(this);
|
||||||
|
|
||||||
|
initializeViews();
|
||||||
|
populateInformationViews();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeViews() {
|
||||||
|
rootLayout = findViewById(R.id.rootLayout);
|
||||||
|
|
||||||
|
ImageView headerBg = findViewById(R.id.headerBg);
|
||||||
|
headerBg.setImageDrawable(new TileDrawable(getRandomHeader(), Shader.TileMode.REPEAT));
|
||||||
|
|
||||||
|
// populate Toolbar (Actionbar)
|
||||||
|
Toolbar myToolbar = findViewById(R.id.toolbar);
|
||||||
|
setSupportActionBar(myToolbar);
|
||||||
|
|
||||||
|
ActionBar ab = getSupportActionBar();
|
||||||
|
if (ab != null) {
|
||||||
|
ab.setDisplayHomeAsUpEnabled(true);
|
||||||
|
ab.setDisplayShowTitleEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
fab = findViewById(R.id.fab);
|
||||||
|
fab.setOnClickListener(onFabClicked());
|
||||||
|
|
||||||
|
fabLoadingDrawable = (AnimatedVectorDrawable) getDrawable(R.drawable.animated_sync);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void populateInformationViews() {
|
||||||
|
Organisation organisation = preferenceManager.getUserOrganisation();
|
||||||
|
|
||||||
|
TextView name = findViewById(R.id.orgName);
|
||||||
|
name.setText(organisation.name);
|
||||||
|
|
||||||
|
MaterialPreferenceText uuid = findViewById(R.id.uuid);
|
||||||
|
uuid.setSubText(organisation.uuid);
|
||||||
|
|
||||||
|
MaterialPreferenceText nationality = findViewById(R.id.nationality);
|
||||||
|
nationality.setSubText(organisation.nationality);
|
||||||
|
|
||||||
|
MaterialPreferenceText sector = findViewById(R.id.sector);
|
||||||
|
if (organisation.sector == null) {
|
||||||
|
sector.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
sector.setSubText(organisation.sector);
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialPreferenceText description = findViewById(R.id.description);
|
||||||
|
description.setSubText(organisation.description);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
getMenuInflater().inflate(R.menu.menu_profile, menu);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
|
||||||
|
if (item.getItemId() == R.id.menu_delete_profile) {
|
||||||
|
clearDeviceAndLogOut();
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
private View.OnClickListener onFabClicked() {
|
||||||
|
return new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
fab.setImageDrawable(fabLoadingDrawable);
|
||||||
|
fabLoadingDrawable.start();
|
||||||
|
updateProfile();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Drawable getRandomHeader() {
|
||||||
|
int[] ids = {R.drawable.ic_bank_note, R.drawable.ic_polka_dots, R.drawable.ic_wiggle, R.drawable.ic_circuit_board};
|
||||||
|
return getDrawable(ids[new Random().nextInt(ids.length)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateProfile() {
|
||||||
|
// progressBar.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
mispRestClient.getMyUser(new MispRestClient.UserCallback() {
|
||||||
|
@Override
|
||||||
|
public void success(final User user) {
|
||||||
|
|
||||||
|
preferenceManager.setUserInfo(user);
|
||||||
|
|
||||||
|
mispRestClient.getOrganisation(user.org_id, new MispRestClient.OrganisationCallback() {
|
||||||
|
@Override
|
||||||
|
public void success(Organisation organisation) {
|
||||||
|
fabLoadingDrawable.stop();
|
||||||
|
preferenceManager.setUserOrgInfo(organisation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void failure(String error) {
|
||||||
|
fabLoadingDrawable.stop();
|
||||||
|
Snackbar.make(rootLayout, error, Snackbar.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void failure(String error) {
|
||||||
|
fabLoadingDrawable.stop();
|
||||||
|
Snackbar.make(rootLayout, error, Snackbar.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearDeviceAndLogOut() {
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
|
|
||||||
|
builder.setTitle("Clear all saved data and logout");
|
||||||
|
builder.setMessage("Do you really want to delete all data and logout?");
|
||||||
|
builder.setNegativeButton("Discard", new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
dialog.cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.setPositiveButton("Delete & Logout", new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
PreferenceManager prefs = PreferenceManager.getInstance(getApplicationContext());
|
||||||
|
prefs.clearAllData();
|
||||||
|
KeyStoreWrapper.deleteAllStoredKeys();
|
||||||
|
|
||||||
|
Intent login = new Intent(getApplicationContext(), LoginActivity.class);
|
||||||
|
startActivity(login);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.create().show();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package lu.circl.mispbump.activities;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import lu.circl.mispbump.auxiliary.PreferenceManager;
|
||||||
|
import lu.circl.mispbump.restful_client.User;
|
||||||
|
|
||||||
|
public class StartUpActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
if (isUserLoggedIn()) {
|
||||||
|
Intent home = new Intent(this, HomeActivity.class);
|
||||||
|
startActivity(home);
|
||||||
|
} else {
|
||||||
|
Intent login = new Intent(this, LoginActivity.class);
|
||||||
|
startActivity(login);
|
||||||
|
}
|
||||||
|
|
||||||
|
// closes the activity to prevent going back to this (empty) activity
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isUserLoggedIn() {
|
||||||
|
PreferenceManager preferenceManager = PreferenceManager.getInstance(this);
|
||||||
|
User user = preferenceManager.getUserInfo();
|
||||||
|
return user != null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,423 @@
|
||||||
|
package lu.circl.mispbump.activities;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.AnimatorListenerAdapter;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
import androidx.fragment.app.FragmentTransaction;
|
||||||
|
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
||||||
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonSyntaxException;
|
||||||
|
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
|
||||||
|
import lu.circl.mispbump.R;
|
||||||
|
import lu.circl.mispbump.auxiliary.PreferenceManager;
|
||||||
|
import lu.circl.mispbump.auxiliary.QrCodeGenerator;
|
||||||
|
import lu.circl.mispbump.auxiliary.RandomString;
|
||||||
|
import lu.circl.mispbump.cam.CameraFragment;
|
||||||
|
import lu.circl.mispbump.custom_views.ExtendedBottomSheetBehavior;
|
||||||
|
import lu.circl.mispbump.fragments.SyncOptionsFragment;
|
||||||
|
import lu.circl.mispbump.models.SyncInformation;
|
||||||
|
import lu.circl.mispbump.models.UploadInformation;
|
||||||
|
import lu.circl.mispbump.security.DiffieHellman;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides the sync functionality.
|
||||||
|
* It collects the necessary information, guides through the process and finally completes with
|
||||||
|
* the upload to the misp instance.
|
||||||
|
*/
|
||||||
|
public class SyncActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
// layout
|
||||||
|
private CoordinatorLayout layout;
|
||||||
|
private ImageView qrCodeView, bottomSheetIcon;
|
||||||
|
private TextView bottomSheetText;
|
||||||
|
private ImageButton prevButton, nextButton;
|
||||||
|
private ExtendedBottomSheetBehavior bottomSheetBehavior;
|
||||||
|
|
||||||
|
// dependencies
|
||||||
|
private PreferenceManager preferenceManager;
|
||||||
|
private DiffieHellman diffieHellman;
|
||||||
|
|
||||||
|
private UploadInformation uploadInformation;
|
||||||
|
|
||||||
|
// fragments
|
||||||
|
private CameraFragment cameraFragment;
|
||||||
|
private SyncOptionsFragment syncOptionsFragment;
|
||||||
|
|
||||||
|
// qr codes
|
||||||
|
private QrCodeGenerator qrCodeGenerator;
|
||||||
|
private Bitmap publicKeyQr, syncInfoQr;
|
||||||
|
|
||||||
|
private SyncState currentSyncState = SyncState.settings;
|
||||||
|
|
||||||
|
private enum SyncState {
|
||||||
|
settings(0),
|
||||||
|
publicKeyExchange(1),
|
||||||
|
dataExchange(2);
|
||||||
|
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
|
||||||
|
SyncState(final int value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_sync);
|
||||||
|
initializeViews();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeViews() {
|
||||||
|
// Root Layout
|
||||||
|
layout = findViewById(R.id.rootLayout);
|
||||||
|
|
||||||
|
// prev button
|
||||||
|
prevButton = findViewById(R.id.prevButton);
|
||||||
|
prevButton.setOnClickListener(onPrevClicked);
|
||||||
|
|
||||||
|
// next button
|
||||||
|
nextButton = findViewById(R.id.nextButton);
|
||||||
|
nextButton.setOnClickListener(onNextClicked);
|
||||||
|
|
||||||
|
// QR Code View
|
||||||
|
qrCodeView = findViewById(R.id.qrcode);
|
||||||
|
qrCodeGenerator = new QrCodeGenerator(SyncActivity.this);
|
||||||
|
|
||||||
|
bottomSheetIcon = findViewById(R.id.bottomSheetIcon);
|
||||||
|
bottomSheetText = findViewById(R.id.bottomSheetText);
|
||||||
|
|
||||||
|
diffieHellman = DiffieHellman.getInstance();
|
||||||
|
preferenceManager = PreferenceManager.getInstance(this);
|
||||||
|
|
||||||
|
View bottomSheet = findViewById(R.id.bottomSheet);
|
||||||
|
bottomSheetBehavior = (ExtendedBottomSheetBehavior) BottomSheetBehavior.from(bottomSheet);
|
||||||
|
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||||
|
bottomSheetBehavior.setSwipeable(false);
|
||||||
|
bottomSheetBehavior.setHideable(false);
|
||||||
|
|
||||||
|
publicKeyQr = generatePublicKeyQr();
|
||||||
|
|
||||||
|
switchState(SyncState.settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when "next button" is pressed
|
||||||
|
*/
|
||||||
|
private View.OnClickListener onNextClicked = new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
switch (currentSyncState) {
|
||||||
|
case settings:
|
||||||
|
uploadInformation.setCached(syncOptionsFragment.cache.isChecked());
|
||||||
|
uploadInformation.setPush(syncOptionsFragment.push.isChecked());
|
||||||
|
uploadInformation.setPull(syncOptionsFragment.pull.isChecked());
|
||||||
|
uploadInformation.setAllowSelfSigned(syncOptionsFragment.allowSelfSigned.isChecked());
|
||||||
|
|
||||||
|
switchState(SyncState.publicKeyExchange);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case publicKeyExchange:
|
||||||
|
switchState(SyncState.dataExchange);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case dataExchange:
|
||||||
|
Intent upload = new Intent(SyncActivity.this, UploadActivity.class);
|
||||||
|
upload.putExtra(UploadActivity.EXTRA_UPLOAD_INFO, new Gson().toJson(uploadInformation));
|
||||||
|
startActivity(upload);
|
||||||
|
overridePendingTransition(R.anim.slide_in_right, android.R.anim.slide_out_right);
|
||||||
|
finish();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when "prev button" is clicked
|
||||||
|
*/
|
||||||
|
private View.OnClickListener onPrevClicked = new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
switch (currentSyncState) {
|
||||||
|
case settings:
|
||||||
|
finish();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case publicKeyExchange:
|
||||||
|
switchState(SyncState.settings);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case dataExchange:
|
||||||
|
switchState(SyncState.publicKeyExchange);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the camera fragment detects a qr code
|
||||||
|
*/
|
||||||
|
private CameraFragment.QrScanCallback onQrCodeScanned = new CameraFragment.QrScanCallback() {
|
||||||
|
@Override
|
||||||
|
public void qrScanResult(String qrData) {
|
||||||
|
cameraFragment.setReadQrEnabled(false);
|
||||||
|
switch (currentSyncState) {
|
||||||
|
case publicKeyExchange:
|
||||||
|
try {
|
||||||
|
final PublicKey pk = DiffieHellman.publicKeyFromString(qrData);
|
||||||
|
diffieHellman.setForeignPublicKey(pk);
|
||||||
|
|
||||||
|
syncInfoQr = generateSyncInfoQr();
|
||||||
|
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
nextButton.setVisibility(View.VISIBLE);
|
||||||
|
cameraFragment.disablePreview();
|
||||||
|
qrReceivedFeedback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
|
||||||
|
Snackbar.make(layout, "Invalid key", Snackbar.LENGTH_SHORT).show();
|
||||||
|
cameraFragment.setReadQrEnabled(true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case dataExchange:
|
||||||
|
cameraFragment.setReadQrEnabled(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final SyncInformation remoteSyncInfo = new Gson().fromJson(diffieHellman.decrypt(qrData), SyncInformation.class);
|
||||||
|
uploadInformation.setRemote(remoteSyncInfo);
|
||||||
|
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
cameraFragment.disablePreview();
|
||||||
|
nextButton.setVisibility(View.VISIBLE);
|
||||||
|
qrReceivedFeedback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (JsonSyntaxException e) {
|
||||||
|
Snackbar.make(layout, "Sync information unreadable", Snackbar.LENGTH_SHORT).show();
|
||||||
|
cameraFragment.setReadQrEnabled(true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
private void switchUiState(SyncState state) {
|
||||||
|
|
||||||
|
bottomSheetIcon.setVisibility(View.INVISIBLE);
|
||||||
|
bottomSheetBehavior.setSwipeable(false);
|
||||||
|
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case settings:
|
||||||
|
prevButton.setImageDrawable(getDrawable(R.drawable.ic_close));
|
||||||
|
prevButton.setVisibility(View.VISIBLE);
|
||||||
|
nextButton.setVisibility(View.VISIBLE);
|
||||||
|
hideQrCode();
|
||||||
|
break;
|
||||||
|
case publicKeyExchange:
|
||||||
|
prevButton.setImageDrawable(getDrawable(R.drawable.ic_arrow_back));
|
||||||
|
prevButton.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
nextButton.setImageDrawable(getDrawable(R.drawable.ic_arrow_forward));
|
||||||
|
nextButton.setVisibility(View.GONE);
|
||||||
|
showQrCode(publicKeyQr);
|
||||||
|
break;
|
||||||
|
case dataExchange:
|
||||||
|
prevButton.setImageDrawable(getDrawable(R.drawable.ic_arrow_back));
|
||||||
|
prevButton.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
nextButton.setImageDrawable(getDrawable(R.drawable.ic_cloud_upload));
|
||||||
|
nextButton.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
cameraFragment.enablePreview();
|
||||||
|
cameraFragment.setReadQrEnabled(true);
|
||||||
|
showQrCode(syncInfoQr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void switchState(SyncState state) {
|
||||||
|
|
||||||
|
FragmentManager fragmentManager = getSupportFragmentManager();
|
||||||
|
FragmentTransaction transaction = fragmentManager.beginTransaction();
|
||||||
|
|
||||||
|
if (currentSyncState != state) {
|
||||||
|
if (state.value < currentSyncState.value) {
|
||||||
|
transaction.setCustomAnimations(android.R.anim.slide_in_left, android.R.anim.slide_out_right);
|
||||||
|
} else {
|
||||||
|
transaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentSyncState = state;
|
||||||
|
|
||||||
|
switchUiState(currentSyncState);
|
||||||
|
|
||||||
|
switch (currentSyncState) {
|
||||||
|
case settings:
|
||||||
|
String fragTag = SyncOptionsFragment.class.getSimpleName();
|
||||||
|
|
||||||
|
syncOptionsFragment = (SyncOptionsFragment) fragmentManager.findFragmentByTag(fragTag);
|
||||||
|
|
||||||
|
if (syncOptionsFragment == null) {
|
||||||
|
syncOptionsFragment = new SyncOptionsFragment();
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.replace(R.id.sync_fragment_container, syncOptionsFragment, fragTag);
|
||||||
|
transaction.commit();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case publicKeyExchange:
|
||||||
|
fragTag = CameraFragment.class.getSimpleName();
|
||||||
|
cameraFragment = (CameraFragment) fragmentManager.findFragmentByTag(fragTag);
|
||||||
|
|
||||||
|
if (cameraFragment == null) {
|
||||||
|
cameraFragment = new CameraFragment();
|
||||||
|
cameraFragment.setOnQrAvailableListener(onQrCodeScanned);
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.replace(R.id.sync_fragment_container, cameraFragment, fragTag);
|
||||||
|
transaction.commit();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case dataExchange:
|
||||||
|
fragTag = CameraFragment.class.getSimpleName();
|
||||||
|
cameraFragment = (CameraFragment) fragmentManager.findFragmentByTag(fragTag);
|
||||||
|
|
||||||
|
if (cameraFragment == null) {
|
||||||
|
cameraFragment = new CameraFragment();
|
||||||
|
cameraFragment.setOnQrAvailableListener(onQrCodeScanned);
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.replace(R.id.sync_fragment_container, cameraFragment, fragTag);
|
||||||
|
transaction.commit();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Bitmap generatePublicKeyQr() {
|
||||||
|
return qrCodeGenerator.generateQrCode(DiffieHellman.publicKeyToString(diffieHellman.getPublicKey()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bitmap generateSyncInfoQr() {
|
||||||
|
SyncInformation syncInformation = new SyncInformation();
|
||||||
|
syncInformation.organisation = preferenceManager.getUserOrganisation().toSyncOrganisation();
|
||||||
|
syncInformation.syncUserAuthkey = new RandomString(40).nextString();
|
||||||
|
syncInformation.baseUrl = preferenceManager.getServerUrl();
|
||||||
|
syncInformation.syncUserPassword = new RandomString(16).nextString();
|
||||||
|
|
||||||
|
String myEmailDomain = preferenceManager.getUserInfo().email.split("@")[1];
|
||||||
|
syncInformation.syncUserEmail = "syncuser_[ORG]@" + myEmailDomain;
|
||||||
|
|
||||||
|
uploadInformation = new UploadInformation(syncInformation);
|
||||||
|
|
||||||
|
// encrypt serialized content
|
||||||
|
String encrypted = diffieHellman.encrypt(new Gson().toJson(syncInformation));
|
||||||
|
|
||||||
|
// generate QR code
|
||||||
|
return qrCodeGenerator.generateQrCode(encrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void showQrCode(final Bitmap bitmap) {
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
qrCodeView.setImageBitmap(bitmap);
|
||||||
|
qrCodeView.setAlpha(0f);
|
||||||
|
qrCodeView.setVisibility(View.VISIBLE);
|
||||||
|
qrCodeView.setScaleX(0.9f);
|
||||||
|
qrCodeView.setScaleY(0.6f);
|
||||||
|
qrCodeView.animate()
|
||||||
|
.scaleX(1f)
|
||||||
|
.scaleY(1f)
|
||||||
|
.alpha(1f)
|
||||||
|
.setDuration(250)
|
||||||
|
.setListener(new AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
qrCodeView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideQrCode() {
|
||||||
|
|
||||||
|
if (qrCodeView.getVisibility() == View.GONE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
qrCodeView.setAlpha(1f);
|
||||||
|
qrCodeView.setVisibility(View.VISIBLE);
|
||||||
|
qrCodeView.setScaleX(1f);
|
||||||
|
qrCodeView.setScaleY(1f);
|
||||||
|
qrCodeView.animate()
|
||||||
|
.scaleX(0f)
|
||||||
|
.scaleY(0f)
|
||||||
|
.alpha(0f)
|
||||||
|
.setDuration(250)
|
||||||
|
.setListener(new AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
qrCodeView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void qrReceivedFeedback() {
|
||||||
|
bottomSheetIcon.setScaleX(0f);
|
||||||
|
bottomSheetIcon.setScaleY(0f);
|
||||||
|
bottomSheetIcon.setVisibility(View.VISIBLE);
|
||||||
|
bottomSheetIcon.animate()
|
||||||
|
.scaleY(1f)
|
||||||
|
.scaleX(1f)
|
||||||
|
.setDuration(250);
|
||||||
|
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
|
||||||
|
bottomSheetBehavior.setSwipeable(true);
|
||||||
|
|
||||||
|
switch (currentSyncState) {
|
||||||
|
case publicKeyExchange:
|
||||||
|
bottomSheetText.setText("Received public key from partner");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case dataExchange:
|
||||||
|
bottomSheetText.setText("Received sync information from partner");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,274 @@
|
||||||
|
package lu.circl.mispbump.activities;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||||
|
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import lu.circl.mispbump.R;
|
||||||
|
import lu.circl.mispbump.auxiliary.DialogManager;
|
||||||
|
import lu.circl.mispbump.auxiliary.PreferenceManager;
|
||||||
|
import lu.circl.mispbump.custom_views.UploadAction;
|
||||||
|
import lu.circl.mispbump.models.UploadInformation;
|
||||||
|
import lu.circl.mispbump.restful_client.MispRestClient;
|
||||||
|
import lu.circl.mispbump.restful_client.MispServer;
|
||||||
|
import lu.circl.mispbump.restful_client.Organisation;
|
||||||
|
import lu.circl.mispbump.restful_client.Server;
|
||||||
|
import lu.circl.mispbump.restful_client.User;
|
||||||
|
|
||||||
|
public class UploadActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
public static final String EXTRA_UPLOAD_INFO = "uploadInformation";
|
||||||
|
|
||||||
|
private PreferenceManager preferenceManager;
|
||||||
|
private MispRestClient restClient;
|
||||||
|
private UploadInformation uploadInformation;
|
||||||
|
|
||||||
|
private CoordinatorLayout rootLayout;
|
||||||
|
|
||||||
|
private UploadAction availableAction, orgAction, userAction, serverAction;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_upload);
|
||||||
|
parseExtra();
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseExtra() {
|
||||||
|
String uploadInfoString = getIntent().getStringExtra(EXTRA_UPLOAD_INFO);
|
||||||
|
uploadInformation = new Gson().fromJson(uploadInfoString, UploadInformation.class);
|
||||||
|
assert uploadInformation != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
preferenceManager = PreferenceManager.getInstance(this);
|
||||||
|
restClient = new MispRestClient(this);
|
||||||
|
rootLayout = findViewById(R.id.rootLayout);
|
||||||
|
|
||||||
|
// toolbar
|
||||||
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
ActionBar ab = getSupportActionBar();
|
||||||
|
assert ab != null;
|
||||||
|
ab.setDisplayShowTitleEnabled(true);
|
||||||
|
ab.setDisplayHomeAsUpEnabled(true);
|
||||||
|
ab.setHomeAsUpIndicator(R.drawable.ic_close);
|
||||||
|
|
||||||
|
// fab
|
||||||
|
FloatingActionButton fab = findViewById(R.id.fab);
|
||||||
|
fab.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
startUpload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
availableAction = findViewById(R.id.availableAction);
|
||||||
|
orgAction = findViewById(R.id.orgAction);
|
||||||
|
userAction = findViewById(R.id.userAction);
|
||||||
|
serverAction = findViewById(R.id.serverAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case android.R.id.home:
|
||||||
|
preferenceManager.addUploadInformation(uploadInformation);
|
||||||
|
finish();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start upload to misp instance.
|
||||||
|
*/
|
||||||
|
private void startUpload() {
|
||||||
|
availableAction.setCurrentUploadState(UploadAction.UploadState.LOADING);
|
||||||
|
restClient.isAvailable(availableCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
private User generateSyncUser(Organisation organisation) {
|
||||||
|
User syncUser = new User();
|
||||||
|
syncUser.org_id = organisation.id;
|
||||||
|
syncUser.role_id = User.ROLE_SYNC_USER;
|
||||||
|
|
||||||
|
String emailSaveOrgName = organisation.name.replace(" ", "").toLowerCase();
|
||||||
|
String syncUserEmailFormat = uploadInformation.getRemote().syncUserEmail;
|
||||||
|
syncUser.email = syncUserEmailFormat.replace("[ORG]", emailSaveOrgName);
|
||||||
|
uploadInformation.getRemote().syncUserEmail = syncUser.email;
|
||||||
|
|
||||||
|
syncUser.password = uploadInformation.getRemote().syncUserPassword;
|
||||||
|
syncUser.authkey = uploadInformation.getRemote().syncUserAuthkey;
|
||||||
|
syncUser.termsaccepted = true;
|
||||||
|
|
||||||
|
return syncUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MispRestClient.AvailableCallback availableCallback = new MispRestClient.AvailableCallback() {
|
||||||
|
@Override
|
||||||
|
public void available() {
|
||||||
|
availableAction.setCurrentUploadState(UploadAction.UploadState.DONE);
|
||||||
|
orgAction.setCurrentUploadState(UploadAction.UploadState.LOADING);
|
||||||
|
|
||||||
|
Thread t = new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
int orgId = organisationExists();
|
||||||
|
if (orgId != -1) {
|
||||||
|
Snackbar.make(rootLayout, "exists", Snackbar.LENGTH_INDEFINITE).show();
|
||||||
|
uploadInformation.getRemote().organisation.id = orgId;
|
||||||
|
// TODO if exists: add User
|
||||||
|
} else {
|
||||||
|
restClient.addOrganisation(uploadInformation.getRemote().organisation, organisationCallback);
|
||||||
|
Snackbar.make(rootLayout, "does not exist", Snackbar.LENGTH_INDEFINITE).show();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Snackbar.make(rootLayout, "Some error", Snackbar.LENGTH_INDEFINITE).show();
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
t.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unavailable(String error) {
|
||||||
|
availableAction.setCurrentUploadState(UploadAction.UploadState.ERROR);
|
||||||
|
availableAction.setError(error);
|
||||||
|
uploadInformation.setCurrentSyncStatus(UploadInformation.SyncStatus.FAILURE);
|
||||||
|
|
||||||
|
Snackbar sb = Snackbar.make(rootLayout, error, Snackbar.LENGTH_INDEFINITE);
|
||||||
|
sb.setAction("Retry", new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
availableAction.setError(null);
|
||||||
|
availableAction.setCurrentUploadState(UploadAction.UploadState.LOADING);
|
||||||
|
startUpload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sb.show();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private MispRestClient.OrganisationCallback organisationCallback = new MispRestClient.OrganisationCallback() {
|
||||||
|
@Override
|
||||||
|
public void success(Organisation organisation) {
|
||||||
|
orgAction.setCurrentUploadState(UploadAction.UploadState.DONE);
|
||||||
|
userAction.setCurrentUploadState(UploadAction.UploadState.LOADING);
|
||||||
|
|
||||||
|
// for later reference in add user callback
|
||||||
|
uploadInformation.getRemote().organisation.id = organisation.id;
|
||||||
|
|
||||||
|
restClient.addUser(generateSyncUser(organisation), userCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void failure(String error) {
|
||||||
|
|
||||||
|
|
||||||
|
// IF error = org already exists:
|
||||||
|
// resClient.addUser()
|
||||||
|
|
||||||
|
orgAction.setCurrentUploadState(UploadAction.UploadState.ERROR);
|
||||||
|
uploadInformation.setCurrentSyncStatus(UploadInformation.SyncStatus.FAILURE);
|
||||||
|
preferenceManager.addUploadInformation(uploadInformation);
|
||||||
|
Snackbar.make(rootLayout, error, Snackbar.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private MispRestClient.UserCallback userCallback = new MispRestClient.UserCallback() {
|
||||||
|
@Override
|
||||||
|
public void success(User user) {
|
||||||
|
userAction.setCurrentUploadState(UploadAction.UploadState.DONE);
|
||||||
|
|
||||||
|
Server server = new Server();
|
||||||
|
server.name = uploadInformation.getRemote().organisation.name + "'s Sync Server";
|
||||||
|
server.url = uploadInformation.getRemote().baseUrl;
|
||||||
|
server.remote_org_id = uploadInformation.getRemote().organisation.id;
|
||||||
|
server.authkey = uploadInformation.getLocal().syncUserAuthkey;
|
||||||
|
server.pull = uploadInformation.isPull();
|
||||||
|
server.push = uploadInformation.isPush();
|
||||||
|
server.caching_enabled = uploadInformation.isCached();
|
||||||
|
server.self_signed = uploadInformation.isAllowSelfSigned();
|
||||||
|
|
||||||
|
restClient.addServer(server, serverCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void failure(String error) {
|
||||||
|
userAction.setCurrentUploadState(UploadAction.UploadState.ERROR);
|
||||||
|
uploadInformation.setCurrentSyncStatus(UploadInformation.SyncStatus.FAILURE);
|
||||||
|
preferenceManager.addUploadInformation(uploadInformation);
|
||||||
|
Snackbar.make(rootLayout, error, Snackbar.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private MispRestClient.ServerCallback serverCallback = new MispRestClient.ServerCallback() {
|
||||||
|
@Override
|
||||||
|
public void success(List<MispServer> servers) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void success(MispServer server) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void success(Server server) {
|
||||||
|
serverAction.setCurrentUploadState(UploadAction.UploadState.DONE);
|
||||||
|
uploadInformation.setCurrentSyncStatus(UploadInformation.SyncStatus.COMPLETE);
|
||||||
|
preferenceManager.addUploadInformation(uploadInformation);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void failure(String error) {
|
||||||
|
serverAction.setCurrentUploadState(UploadAction.UploadState.ERROR);
|
||||||
|
uploadInformation.setCurrentSyncStatus(UploadInformation.SyncStatus.FAILURE);
|
||||||
|
preferenceManager.addUploadInformation(uploadInformation);
|
||||||
|
Snackbar.make(rootLayout, error, Snackbar.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
private int organisationExists() throws IOException {
|
||||||
|
final UUID uuidToCheck = UUID.fromString(uploadInformation.getRemote().organisation.uuid);
|
||||||
|
|
||||||
|
Organisation[] organisations = restClient.getAllOrganisations();
|
||||||
|
|
||||||
|
if (organisations != null) {
|
||||||
|
for (Organisation organisation : organisations) {
|
||||||
|
if (uuidToCheck.compareTo(UUID.fromString(organisation.uuid)) == 0) {
|
||||||
|
return organisation.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int userExists() {
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,151 @@
|
||||||
|
package lu.circl.mispbump.adapters;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
|
import androidx.core.widget.ImageViewCompat;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import android.content.res.ColorStateList;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import lu.circl.mispbump.R;
|
||||||
|
import lu.circl.mispbump.custom_views.MaterialPreferenceText;
|
||||||
|
import lu.circl.mispbump.interfaces.IOnItemClickListener;
|
||||||
|
import lu.circl.mispbump.models.UploadInformation;
|
||||||
|
|
||||||
|
public class SyncAdapter extends RecyclerView.Adapter<SyncAdapter.SyncViewHolder> {
|
||||||
|
|
||||||
|
private Context context;
|
||||||
|
private List<UploadInformation> uploadInformationList;
|
||||||
|
private IOnItemClickListener<UploadInformation> deleteListener, retryListener;
|
||||||
|
|
||||||
|
static class SyncViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
MaterialPreferenceText email, password;
|
||||||
|
TextView orgName, date;
|
||||||
|
ImageView syncStatus;
|
||||||
|
ImageButton retry, delete;
|
||||||
|
|
||||||
|
ConstraintLayout collapsedContent, expandedContent;
|
||||||
|
|
||||||
|
SyncViewHolder(View v) {
|
||||||
|
super(v);
|
||||||
|
|
||||||
|
expandedContent = v.findViewById(R.id.expandedContent);
|
||||||
|
expandedContent.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
collapsedContent = v.findViewById(R.id.collapsedContent);
|
||||||
|
collapsedContent.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
if (expandedContent.getVisibility() == View.GONE) {
|
||||||
|
expandedContent.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
expandedContent.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
orgName = v.findViewById(R.id.orgName);
|
||||||
|
date = v.findViewById(R.id.date);
|
||||||
|
|
||||||
|
email = v.findViewById(R.id.email);
|
||||||
|
password = v.findViewById(R.id.password);
|
||||||
|
|
||||||
|
syncStatus = v.findViewById(R.id.syncStatus);
|
||||||
|
|
||||||
|
retry = v.findViewById(R.id.retryButton);
|
||||||
|
delete = v.findViewById(R.id.deleteButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bindDeleteListener(final UploadInformation item, final IOnItemClickListener<UploadInformation> listener) {
|
||||||
|
delete.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
listener.onItemClick(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void bindRetryListener(final UploadInformation item, final IOnItemClickListener<UploadInformation> listener) {
|
||||||
|
retry.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
listener.onItemClick(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SyncAdapter(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUploadInformationList(List<UploadInformation> uploadInformationList) {
|
||||||
|
this.uploadInformationList = uploadInformationList;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnDeleteClickListener(IOnItemClickListener<UploadInformation> listener) {
|
||||||
|
deleteListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnRetryClickListener(IOnItemClickListener<UploadInformation> listener) {
|
||||||
|
retryListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public SyncViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
|
||||||
|
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.viewholder_sync, viewGroup, false);
|
||||||
|
return new SyncViewHolder(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull SyncViewHolder syncViewHolder, int i) {
|
||||||
|
|
||||||
|
syncViewHolder.orgName.setText(uploadInformationList.get(i).getRemote().organisation.name);
|
||||||
|
syncViewHolder.date.setText(uploadInformationList.get(i).getDateString());
|
||||||
|
syncViewHolder.email.setSubText(uploadInformationList.get(i).getRemote().syncUserEmail);
|
||||||
|
syncViewHolder.password.setSubText(uploadInformationList.get(i).getRemote().syncUserPassword);
|
||||||
|
|
||||||
|
switch (uploadInformationList.get(i).getCurrentSyncStatus()) {
|
||||||
|
case COMPLETE:
|
||||||
|
ImageViewCompat.setImageTintList(syncViewHolder.syncStatus, ColorStateList.valueOf(context.getColor(R.color.status_green)));
|
||||||
|
syncViewHolder.syncStatus.setImageResource(R.drawable.ic_check);
|
||||||
|
syncViewHolder.retry.setVisibility(View.GONE);
|
||||||
|
break;
|
||||||
|
case FAILURE:
|
||||||
|
ImageViewCompat.setImageTintList(syncViewHolder.syncStatus, ColorStateList.valueOf(context.getColor(R.color.status_red)));
|
||||||
|
syncViewHolder.syncStatus.setImageResource(R.drawable.ic_error_outline);
|
||||||
|
syncViewHolder.retry.setVisibility(View.VISIBLE);
|
||||||
|
break;
|
||||||
|
case PENDING:
|
||||||
|
ImageViewCompat.setImageTintList(syncViewHolder.syncStatus, ColorStateList.valueOf(context.getColor(R.color.status_amber)));
|
||||||
|
syncViewHolder.syncStatus.setImageResource(R.drawable.ic_error_outline);
|
||||||
|
syncViewHolder.retry.setVisibility(View.VISIBLE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
syncViewHolder.bindDeleteListener(uploadInformationList.get(i), deleteListener);
|
||||||
|
syncViewHolder.bindRetryListener(uploadInformationList.get(i), retryListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
if (uploadInformationList == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return uploadInformationList.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package lu.circl.mispbump.adapters;
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import lu.circl.mispbump.R;
|
||||||
|
import lu.circl.mispbump.auxiliary.KeyValue;
|
||||||
|
import lu.circl.mispbump.restful_client.Organisation;
|
||||||
|
import lu.circl.mispbump.restful_client.User;
|
||||||
|
|
||||||
|
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserViewHolder> {
|
||||||
|
|
||||||
|
private List<KeyValue<String, String>> data = new ArrayList<>();
|
||||||
|
|
||||||
|
static class UserViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
TextView title;
|
||||||
|
TextView description;
|
||||||
|
|
||||||
|
UserViewHolder(View v) {
|
||||||
|
super(v);
|
||||||
|
title = v.findViewById(R.id.viewholder_user_title);
|
||||||
|
description = v.findViewById(R.id.viewholder_user_description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAdapter(User user, Organisation organisation) {
|
||||||
|
data.add(new KeyValue<>("UUID", organisation.uuid));
|
||||||
|
data.add(new KeyValue<>("Name", organisation.name));
|
||||||
|
data.add(new KeyValue<>("Description", organisation.description));
|
||||||
|
data.add(new KeyValue<>("Nationality", organisation.nationality));
|
||||||
|
data.add(new KeyValue<>("Email", user.email));
|
||||||
|
// data.add(new KeyValue<>("ID", "" + user.value));
|
||||||
|
// data.add(new KeyValue<>("Organisation ID", "" + user.org_id));
|
||||||
|
// data.add(new KeyValue<>("Role ID", "" + user.role_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserAdapter.UserViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.viewholder_user, parent, false);
|
||||||
|
return new UserViewHolder(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(UserViewHolder holder, int position) {
|
||||||
|
holder.title.setText(data.get(position).key);
|
||||||
|
holder.description.setText(data.get(position).value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return data.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,267 @@
|
||||||
|
package lu.circl.mispbump.auxiliary;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
|
import java.security.PublicKey;
|
||||||
|
|
||||||
|
import lu.circl.mispbump.R;
|
||||||
|
import lu.circl.mispbump.models.SyncInformation;
|
||||||
|
import lu.circl.mispbump.security.DiffieHellman;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and show dialogs.
|
||||||
|
* Automatically takes care of using the UI Thread.
|
||||||
|
*/
|
||||||
|
public class DialogManager {
|
||||||
|
|
||||||
|
|
||||||
|
public static void saveAndExitDialog(Context context, final IDialogFeedback callback) {
|
||||||
|
final AlertDialog.Builder adb = new AlertDialog.Builder(context);
|
||||||
|
|
||||||
|
adb.setTitle("Save before exit?");
|
||||||
|
adb.setMessage("Saved syncs can be accessed from the main menu.");
|
||||||
|
adb.setPositiveButton("Save and Exit", new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.positive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
adb.setNegativeButton("Exit", new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.negative();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Activity act = (Activity) context;
|
||||||
|
act.runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
adb.create().show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dialog to display a received public key.
|
||||||
|
*
|
||||||
|
* @param publicKey the public key to display
|
||||||
|
* @param context needed to build and show the dialog
|
||||||
|
* @param callback {@link IDialogFeedback}
|
||||||
|
*/
|
||||||
|
public static void publicKeyDialog(PublicKey publicKey, Context context, final IDialogFeedback callback) {
|
||||||
|
final AlertDialog.Builder adb = new AlertDialog.Builder(context);
|
||||||
|
|
||||||
|
adb.setTitle("Public Key");
|
||||||
|
|
||||||
|
String message = "Algorithm: " + publicKey.getAlgorithm() + "\n" +
|
||||||
|
"Format: " + publicKey.getFormat() + "\n" +
|
||||||
|
"Content: \n" + DiffieHellman.publicKeyToString(publicKey);
|
||||||
|
|
||||||
|
adb.setMessage(message);
|
||||||
|
adb.setPositiveButton("Okay", new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.positive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Activity act = (Activity) context;
|
||||||
|
|
||||||
|
act.runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
adb.create().show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dialog to display a received public key.
|
||||||
|
*
|
||||||
|
* @param syncInformation {@link SyncInformation}
|
||||||
|
* @param context needed to build and show the dialog
|
||||||
|
* @param callback {@link IDialogFeedback}
|
||||||
|
*/
|
||||||
|
public static void syncInformationDialog(SyncInformation syncInformation, Context context, final IDialogFeedback callback) {
|
||||||
|
|
||||||
|
final AlertDialog.Builder adb = new AlertDialog.Builder(context);
|
||||||
|
|
||||||
|
adb.setTitle("Sync information received");
|
||||||
|
adb.setMessage(syncInformation.organisation.name);
|
||||||
|
adb.setPositiveButton("Accept", new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.positive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
adb.setNegativeButton("Reject", new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.negative();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Activity act = (Activity) context;
|
||||||
|
|
||||||
|
act.runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
adb.create().show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dialog to ask the user if his sync partner already scanned the displayed qr code.
|
||||||
|
*
|
||||||
|
* @param context needed to build and show the dialog
|
||||||
|
* @param callback {@link IDialogFeedback}
|
||||||
|
*/
|
||||||
|
public static void confirmProceedDialog(Context context, final IDialogFeedback callback) {
|
||||||
|
final AlertDialog.Builder adb = new AlertDialog.Builder(context);
|
||||||
|
|
||||||
|
adb.setTitle("Continue?");
|
||||||
|
adb.setMessage("Only continue if your partner already scanned this QR code");
|
||||||
|
adb.setPositiveButton("Continue", new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.positive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
adb.setNegativeButton("Show QR code again", new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.negative();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Activity act = (Activity) context;
|
||||||
|
act.runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
adb.create().show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dialog to provide login information.
|
||||||
|
*
|
||||||
|
* @param context needed to build and show the dialog
|
||||||
|
*/
|
||||||
|
public static void loginHelpDialog(Context context) {
|
||||||
|
final AlertDialog.Builder adb = new AlertDialog.Builder(context);
|
||||||
|
// adb.setTitle(R.string.app_name);
|
||||||
|
adb.setMessage(R.string.login_help_text);
|
||||||
|
adb.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Activity act = (Activity) context;
|
||||||
|
|
||||||
|
act.runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
adb.create().show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void instanceNotAvailableDialog(Context context, final IDialogFeedback callback) {
|
||||||
|
final AlertDialog.Builder adb = new AlertDialog.Builder(context);
|
||||||
|
|
||||||
|
adb.setTitle("MISP not available");
|
||||||
|
adb.setMessage("Your MISP instance is not available. Would you like to save?");
|
||||||
|
adb.setPositiveButton("Retry now", new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.positive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
adb.setNegativeButton("Save & retry later", new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.negative();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Activity act = (Activity) context;
|
||||||
|
act.runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
adb.create().show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void deleteSyncInformationDialog(Context context, final IDialogFeedback callback) {
|
||||||
|
final AlertDialog.Builder adb = new AlertDialog.Builder(context);
|
||||||
|
|
||||||
|
adb.setTitle("Delete Sync Information?");
|
||||||
|
adb.setMessage("This sync information will be deleted permanently");
|
||||||
|
adb.setPositiveButton("Delete", new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.positive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
adb.setNegativeButton("Discard", new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.negative();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Activity act = (Activity) context;
|
||||||
|
act.runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
adb.create().show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface to give feedback about the user choice in dialogs.
|
||||||
|
*/
|
||||||
|
public interface IDialogFeedback {
|
||||||
|
void positive();
|
||||||
|
void negative();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package lu.circl.mispbump.auxiliary;
|
||||||
|
|
||||||
|
public class KeyValue<K, V> {
|
||||||
|
public K key;
|
||||||
|
public V value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a generic key value pair.
|
||||||
|
* @param key key
|
||||||
|
* @param value value
|
||||||
|
*/
|
||||||
|
public KeyValue(K key, V value) {
|
||||||
|
this.key = key;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,471 @@
|
||||||
|
package lu.circl.mispbump.auxiliary;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import javax.crypto.BadPaddingException;
|
||||||
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
|
import javax.crypto.NoSuchPaddingException;
|
||||||
|
|
||||||
|
import lu.circl.mispbump.models.UploadInformation;
|
||||||
|
import lu.circl.mispbump.restful_client.Organisation;
|
||||||
|
import lu.circl.mispbump.restful_client.User;
|
||||||
|
import lu.circl.mispbump.security.KeyStoreWrapper;
|
||||||
|
|
||||||
|
public class PreferenceManager {
|
||||||
|
|
||||||
|
private static final String TAG = "PreferenceManager";
|
||||||
|
|
||||||
|
private static final String PREFERENCES_FILE = "user_settings";
|
||||||
|
|
||||||
|
private static final String SAVE_CREDENTIALS = "save_credentials";
|
||||||
|
private static final String SERVER_URL = "server_url";
|
||||||
|
private static final String AUTOMATION_KEY = "user_automation";
|
||||||
|
|
||||||
|
private static final String USER_INFOS = "user_infos";
|
||||||
|
private static final String USER_ORG_INFOS = "user_org_infos";
|
||||||
|
|
||||||
|
private static final String UPLOAD_INFO = "upload_info";
|
||||||
|
|
||||||
|
private SharedPreferences preferences;
|
||||||
|
private static PreferenceManager instance;
|
||||||
|
|
||||||
|
private PreferenceManager(Context context) {
|
||||||
|
preferences = context.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class to save and retrieve (sensitive) information to and from SharedPreferences.
|
||||||
|
*
|
||||||
|
* @param context for accessing the SharedPreferences file.
|
||||||
|
* @return singleton instance
|
||||||
|
*/
|
||||||
|
public static PreferenceManager getInstance(Context context) {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new PreferenceManager(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves user infos from "users/view/me" (encrypted)
|
||||||
|
*
|
||||||
|
* @param user {@link User}
|
||||||
|
*/
|
||||||
|
public void setUserInfo(User user) {
|
||||||
|
try {
|
||||||
|
String userStr = new Gson().toJson(user);
|
||||||
|
KeyStoreWrapper keyStoreWrapper = new KeyStoreWrapper(KeyStoreWrapper.USER_INFO_ALIAS);
|
||||||
|
String encryptedUserInfo = keyStoreWrapper.encrypt(userStr);
|
||||||
|
SharedPreferences.Editor editor = preferences.edit();
|
||||||
|
editor.putString(USER_INFOS, encryptedUserInfo);
|
||||||
|
editor.apply();
|
||||||
|
} catch (NoSuchPaddingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (BadPaddingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IllegalBlockSizeException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the user information if already stored and decrypts it.
|
||||||
|
*
|
||||||
|
* @return decrypted user info if any, else null
|
||||||
|
*/
|
||||||
|
public User getUserInfo() {
|
||||||
|
|
||||||
|
if (!preferences.contains(USER_INFOS)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
KeyStoreWrapper keyStoreWrapper = new KeyStoreWrapper(KeyStoreWrapper.USER_INFO_ALIAS);
|
||||||
|
String decrypted = keyStoreWrapper.decrypt(preferences.getString(USER_INFOS, ""));
|
||||||
|
return new Gson().fromJson(decrypted, User.class);
|
||||||
|
} catch (NoSuchPaddingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (InvalidAlgorithmParameterException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (BadPaddingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IllegalBlockSizeException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save user org infos from "organisations/view/{orgId}" (encrypted)
|
||||||
|
*
|
||||||
|
* @param organisation Object representation of json organisation information
|
||||||
|
*/
|
||||||
|
public void setUserOrgInfo(Organisation organisation) {
|
||||||
|
try {
|
||||||
|
String orgStr = new Gson().toJson(organisation);
|
||||||
|
KeyStoreWrapper keyStoreWrapper = new KeyStoreWrapper(KeyStoreWrapper.USER_ORGANISATION_INFO_ALIAS);
|
||||||
|
String encrypted = keyStoreWrapper.encrypt(orgStr);
|
||||||
|
SharedPreferences.Editor editor = preferences.edit();
|
||||||
|
editor.putString(USER_ORG_INFOS, encrypted);
|
||||||
|
editor.apply();
|
||||||
|
} catch (NoSuchPaddingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (BadPaddingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IllegalBlockSizeException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the user organisation information if already stored and decrypts it.
|
||||||
|
*
|
||||||
|
* @return decrypted user org info if any, else null
|
||||||
|
*/
|
||||||
|
public Organisation getUserOrganisation() {
|
||||||
|
|
||||||
|
if (!preferences.contains(USER_ORG_INFOS)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
KeyStoreWrapper keyStoreWrapper = new KeyStoreWrapper(KeyStoreWrapper.USER_ORGANISATION_INFO_ALIAS);
|
||||||
|
String decrypted = keyStoreWrapper.decrypt(preferences.getString(USER_ORG_INFOS, ""));
|
||||||
|
return new Gson().fromJson(decrypted, Organisation.class);
|
||||||
|
} catch (NoSuchPaddingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (InvalidAlgorithmParameterException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (BadPaddingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IllegalBlockSizeException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypts the automation key and stores it in preferences.
|
||||||
|
*
|
||||||
|
* @param automationKey key entered in {@link lu.circl.mispbump.activities.LoginActivity}
|
||||||
|
*/
|
||||||
|
public void setAutomationKey(String automationKey) {
|
||||||
|
try {
|
||||||
|
KeyStoreWrapper keyStoreWrapper = new KeyStoreWrapper(KeyStoreWrapper.AUTOMATION_ALIAS);
|
||||||
|
SharedPreferences.Editor editor = preferences.edit();
|
||||||
|
editor.putString(AUTOMATION_KEY, keyStoreWrapper.encrypt(automationKey));
|
||||||
|
editor.apply();
|
||||||
|
} catch (NoSuchPaddingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (BadPaddingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IllegalBlockSizeException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts the stored automation key and returns it.
|
||||||
|
*
|
||||||
|
* @return decrypted automation key associated with the current user. If no user exists an empty
|
||||||
|
* String is returned.
|
||||||
|
*/
|
||||||
|
public String getAutomationKey() {
|
||||||
|
|
||||||
|
if (!preferences.contains(AUTOMATION_KEY)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
KeyStoreWrapper keyStoreWrapper = new KeyStoreWrapper(KeyStoreWrapper.AUTOMATION_ALIAS);
|
||||||
|
return keyStoreWrapper.decrypt(preferences.getString(AUTOMATION_KEY, ""));
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (InvalidAlgorithmParameterException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (NoSuchPaddingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (BadPaddingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IllegalBlockSizeException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the key to decrypt this entry and the entry itself.
|
||||||
|
*/
|
||||||
|
public void clearAutomationKey() {
|
||||||
|
// remove the key from KeyStore
|
||||||
|
KeyStoreWrapper keyStoreWrapper = new KeyStoreWrapper(KeyStoreWrapper.AUTOMATION_ALIAS);
|
||||||
|
keyStoreWrapper.deleteStoredKey();
|
||||||
|
|
||||||
|
SharedPreferences.Editor editor = preferences.edit();
|
||||||
|
editor.remove(AUTOMATION_KEY);
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypts the server url and stores it in preferences.
|
||||||
|
*
|
||||||
|
* @param serverUrl url of the corresponding misp instance
|
||||||
|
*/
|
||||||
|
public void setServerUrl(String serverUrl) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
KeyStoreWrapper keyStoreWrapper = new KeyStoreWrapper(KeyStoreWrapper.SERVER_URL_ALIAS);
|
||||||
|
SharedPreferences.Editor editor = preferences.edit();
|
||||||
|
editor.putString(SERVER_URL, keyStoreWrapper.encrypt(serverUrl));
|
||||||
|
editor.apply();
|
||||||
|
|
||||||
|
} catch (NoSuchPaddingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (BadPaddingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IllegalBlockSizeException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts the stored server url and returns it
|
||||||
|
*
|
||||||
|
* @return decrypted misp instance url
|
||||||
|
*/
|
||||||
|
public String getServerUrl() {
|
||||||
|
|
||||||
|
if (!preferences.contains(SERVER_URL)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
KeyStoreWrapper keyStoreWrapper = new KeyStoreWrapper(KeyStoreWrapper.SERVER_URL_ALIAS);
|
||||||
|
return keyStoreWrapper.decrypt(preferences.getString(SERVER_URL, ""));
|
||||||
|
|
||||||
|
} catch (NoSuchPaddingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (InvalidAlgorithmParameterException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (BadPaddingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IllegalBlockSizeException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the key to decrypt this entry and the entry itself.
|
||||||
|
*/
|
||||||
|
public void clearServerUrl() {
|
||||||
|
// remove the key from KeyStore
|
||||||
|
KeyStoreWrapper keyStoreWrapper = new KeyStoreWrapper(KeyStoreWrapper.SERVER_URL_ALIAS);
|
||||||
|
keyStoreWrapper.deleteStoredKey();
|
||||||
|
|
||||||
|
SharedPreferences.Editor editor = preferences.edit();
|
||||||
|
editor.remove(SERVER_URL);
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setUploadInformationList(List<UploadInformation> uploadInformationList) {
|
||||||
|
KeyStoreWrapper ksw = new KeyStoreWrapper(KeyStoreWrapper.UPLOAD_INFORMATION_ALIAS);
|
||||||
|
|
||||||
|
try {
|
||||||
|
String cipherText = ksw.encrypt(new Gson().toJson(uploadInformationList));
|
||||||
|
SharedPreferences.Editor editor = preferences.edit();
|
||||||
|
editor.putString(UPLOAD_INFO, cipherText);
|
||||||
|
editor.apply();
|
||||||
|
} catch (NoSuchPaddingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (BadPaddingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IllegalBlockSizeException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<UploadInformation> getUploadInformation() {
|
||||||
|
KeyStoreWrapper ksw = new KeyStoreWrapper(KeyStoreWrapper.UPLOAD_INFORMATION_ALIAS);
|
||||||
|
String storedUploadInfoString = preferences.getString(UPLOAD_INFO, null);
|
||||||
|
|
||||||
|
Type type = new TypeToken<List<UploadInformation>>() {}.getType();
|
||||||
|
|
||||||
|
if (storedUploadInfoString == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
storedUploadInfoString = ksw.decrypt(storedUploadInfoString);
|
||||||
|
} catch (NoSuchPaddingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (InvalidAlgorithmParameterException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (BadPaddingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IllegalBlockSizeException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Gson().fromJson(storedUploadInfoString, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addUploadInformation(UploadInformation uploadInformation) {
|
||||||
|
List<UploadInformation> uploadInformationList = getUploadInformation();
|
||||||
|
|
||||||
|
if (uploadInformationList == null) {
|
||||||
|
uploadInformationList = new ArrayList<>();
|
||||||
|
uploadInformationList.add(uploadInformation);
|
||||||
|
setUploadInformationList(uploadInformationList);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// check if upload information already exists
|
||||||
|
for (int i = 0; i < uploadInformationList.size(); i++) {
|
||||||
|
if (uploadInformationList.get(i).getId().compareTo(uploadInformation.getId()) == 0) {
|
||||||
|
uploadInformationList.set(i, uploadInformation);
|
||||||
|
setUploadInformationList(uploadInformationList);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadInformationList.add(uploadInformation);
|
||||||
|
setUploadInformationList(uploadInformationList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsUploadInformation(UUID uuid) {
|
||||||
|
List<UploadInformation> uploadInformationList = getUploadInformation();
|
||||||
|
|
||||||
|
if (uploadInformationList == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (UploadInformation ui : uploadInformationList) {
|
||||||
|
if (ui.getId().compareTo(uuid) == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeUploadInformation(UUID uuid) {
|
||||||
|
Log.d("PREFS", "uuid to delete: " + uuid.toString());
|
||||||
|
|
||||||
|
List<UploadInformation> uploadInformationList = getUploadInformation();
|
||||||
|
|
||||||
|
for (UploadInformation ui : uploadInformationList) {
|
||||||
|
|
||||||
|
Log.d("PREFS", "checking uuid: " + ui.getId().toString());
|
||||||
|
|
||||||
|
if (ui.getId().compareTo(uuid) == 0) {
|
||||||
|
if (uploadInformationList.size() == 1) {
|
||||||
|
clearUploadInformation();
|
||||||
|
} else {
|
||||||
|
uploadInformationList.remove(ui);
|
||||||
|
setUploadInformationList(uploadInformationList);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearUploadInformation() {
|
||||||
|
KeyStoreWrapper keyStoreWrapper = new KeyStoreWrapper(KeyStoreWrapper.UPLOAD_INFORMATION_ALIAS);
|
||||||
|
keyStoreWrapper.deleteStoredKey();
|
||||||
|
|
||||||
|
SharedPreferences.Editor editor = preferences.edit();
|
||||||
|
editor.remove(UPLOAD_INFO);
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set if credentials (authkey & server url) should be saved locally.
|
||||||
|
*
|
||||||
|
* @param save enable or disable
|
||||||
|
* @deprecated currently not used because automation key is needed to do requests to your misp instance.
|
||||||
|
* If this should be an option in future: misp automation key would be needed on each sync process.
|
||||||
|
*/
|
||||||
|
public void setSaveCredentials(boolean save) {
|
||||||
|
SharedPreferences.Editor editor = preferences.edit();
|
||||||
|
editor.putBoolean(SAVE_CREDENTIALS, save);
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getSaveCredentials() {
|
||||||
|
return preferences.getBoolean(SAVE_CREDENTIALS, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void clearAllData() {
|
||||||
|
SharedPreferences.Editor editor = preferences.edit();
|
||||||
|
editor.clear();
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package lu.circl.mispbump.auxiliary;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Point;
|
||||||
|
|
||||||
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
import com.google.zxing.EncodeHintType;
|
||||||
|
import com.google.zxing.MultiFormatWriter;
|
||||||
|
import com.google.zxing.WriterException;
|
||||||
|
import com.google.zxing.common.BitMatrix;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class QrCodeGenerator {
|
||||||
|
|
||||||
|
private Activity callingActivity;
|
||||||
|
|
||||||
|
public QrCodeGenerator(Activity callingActivity) {
|
||||||
|
this.callingActivity = callingActivity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bitmap generateQrCode(String content) {
|
||||||
|
Point displaySize = new Point();
|
||||||
|
callingActivity.getWindowManager().getDefaultDisplay().getSize(displaySize);
|
||||||
|
|
||||||
|
int size = displaySize.x;
|
||||||
|
|
||||||
|
if (displaySize.x > displaySize.y) {
|
||||||
|
size = displaySize.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
size = (int)(size * 0.8);
|
||||||
|
|
||||||
|
try {
|
||||||
|
MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
|
||||||
|
|
||||||
|
Map<EncodeHintType, Integer> hints = new HashMap<>();
|
||||||
|
hints.put(EncodeHintType.MARGIN, 0);
|
||||||
|
|
||||||
|
BitMatrix bitMatrix = multiFormatWriter.encode(content, BarcodeFormat.QR_CODE, size, size, hints);
|
||||||
|
return createBitmap(bitMatrix);
|
||||||
|
} catch (WriterException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bitmap createBitmap(BitMatrix matrix) {
|
||||||
|
int width = matrix.getWidth();
|
||||||
|
int height = matrix.getHeight();
|
||||||
|
int[] pixels = new int[width * height];
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
int offset = y * width;
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
pixels[offset + x] = matrix.get(x, y) ? 0xFF000000 : 0x99FFFFFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||||
|
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package lu.circl.mispbump.auxiliary;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
public class RandomString {
|
||||||
|
@SuppressWarnings("SpellCheckingInspection")
|
||||||
|
private static final String upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
|
private static final String lower = upper.toLowerCase(Locale.ROOT);
|
||||||
|
private static final String digits = "0123456789";
|
||||||
|
private static final String alphaNum = upper + lower + digits;
|
||||||
|
|
||||||
|
private final Random random;
|
||||||
|
private final char[] symbols;
|
||||||
|
private final char[] buf;
|
||||||
|
|
||||||
|
public RandomString(int length) {
|
||||||
|
this(length, new SecureRandom());
|
||||||
|
}
|
||||||
|
|
||||||
|
RandomString(int length, Random random) {
|
||||||
|
this(length, random, alphaNum);
|
||||||
|
}
|
||||||
|
|
||||||
|
RandomString(int length, Random random, String symbols) {
|
||||||
|
if (length < 1) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
if (symbols.length() < 2) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.random = Objects.requireNonNull(random);
|
||||||
|
this.symbols = symbols.toCharArray();
|
||||||
|
this.buf = new char[length];
|
||||||
|
}
|
||||||
|
|
||||||
|
public String nextString() {
|
||||||
|
for (int idx = 0; idx < buf.length; ++idx) {
|
||||||
|
buf[idx] = symbols[random.nextInt(symbols.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new String(buf);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
package lu.circl.mispbump.auxiliary
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapShader
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.ColorFilter
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.PixelFormat
|
||||||
|
import android.graphics.Shader
|
||||||
|
import android.graphics.drawable.BitmapDrawable
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
|
||||||
|
class TileDrawable(drawable: Drawable, tileMode: Shader.TileMode) : Drawable() {
|
||||||
|
|
||||||
|
private val paint: Paint
|
||||||
|
|
||||||
|
init {
|
||||||
|
paint = Paint().apply {
|
||||||
|
shader = BitmapShader(getBitmap(drawable), tileMode, tileMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun draw(canvas: Canvas) {
|
||||||
|
canvas.drawPaint(paint)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setAlpha(alpha: Int) {
|
||||||
|
paint.alpha = alpha
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getOpacity() = PixelFormat.TRANSLUCENT
|
||||||
|
|
||||||
|
override fun setColorFilter(colorFilter: ColorFilter?) {
|
||||||
|
paint.colorFilter = colorFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getBitmap(drawable: Drawable): Bitmap {
|
||||||
|
if (drawable is BitmapDrawable) {
|
||||||
|
return drawable.bitmap
|
||||||
|
}
|
||||||
|
val bmp = Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight,
|
||||||
|
Bitmap.Config.ARGB_8888)
|
||||||
|
val c = Canvas(bmp)
|
||||||
|
drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
|
||||||
|
drawable.draw(c)
|
||||||
|
return bmp
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
package lu.circl.mispbump.cam;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2014 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.TextureView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link TextureView} that can be adjusted to a specified aspect ratio.
|
||||||
|
*/
|
||||||
|
public class AutoFitTextureView extends TextureView {
|
||||||
|
|
||||||
|
private int mRatioWidth = 0;
|
||||||
|
private int mRatioHeight = 0;
|
||||||
|
|
||||||
|
public AutoFitTextureView(Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AutoFitTextureView(Context context, AttributeSet attrs) {
|
||||||
|
this(context, attrs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) {
|
||||||
|
super(context, attrs, defStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
|
||||||
|
* calculated from the parameters. Note that the actual sizes of parameters don't matter, that
|
||||||
|
* is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
|
||||||
|
*
|
||||||
|
* @param width Relative horizontal size
|
||||||
|
* @param height Relative vertical size
|
||||||
|
*/
|
||||||
|
public void setAspectRatio(int width, int height) {
|
||||||
|
if (width < 0 || height < 0) {
|
||||||
|
throw new IllegalArgumentException("Size cannot be negative.");
|
||||||
|
}
|
||||||
|
mRatioWidth = width;
|
||||||
|
mRatioHeight = height;
|
||||||
|
requestLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
|
int width = MeasureSpec.getSize(widthMeasureSpec);
|
||||||
|
int height = MeasureSpec.getSize(heightMeasureSpec);
|
||||||
|
if (0 == mRatioWidth || 0 == mRatioHeight) {
|
||||||
|
setMeasuredDimension(width, height);
|
||||||
|
} else {
|
||||||
|
if (width < height * mRatioWidth / mRatioHeight) {
|
||||||
|
setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
|
||||||
|
} else {
|
||||||
|
setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
package de.overview.wg.its.mispbump.cam;
|
package lu.circl.mispbump.cam;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.AnimatorListenerAdapter;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
|
@ -8,7 +10,12 @@ import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.graphics.*;
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.ImageFormat;
|
||||||
|
import android.graphics.Matrix;
|
||||||
|
import android.graphics.Point;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.graphics.SurfaceTexture;
|
||||||
import android.hardware.camera2.CameraAccessException;
|
import android.hardware.camera2.CameraAccessException;
|
||||||
import android.hardware.camera2.CameraCaptureSession;
|
import android.hardware.camera2.CameraCaptureSession;
|
||||||
import android.hardware.camera2.CameraCharacteristics;
|
import android.hardware.camera2.CameraCharacteristics;
|
||||||
|
@ -21,23 +28,31 @@ import android.media.ImageReader;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.renderscript.*;
|
import android.renderscript.Allocation;
|
||||||
import android.support.annotation.NonNull;
|
import android.renderscript.Element;
|
||||||
import android.support.v4.app.ActivityCompat;
|
import android.renderscript.RenderScript;
|
||||||
import android.support.v4.app.DialogFragment;
|
import android.renderscript.ScriptIntrinsicYuvToRGB;
|
||||||
import android.support.v4.app.Fragment;
|
import android.renderscript.Type;
|
||||||
import android.support.v4.content.ContextCompat;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Size;
|
import android.util.Size;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import android.util.SparseIntArray;
|
import android.util.SparseIntArray;
|
||||||
import android.view.*;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Surface;
|
||||||
|
import android.view.TextureView;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.app.ActivityCompat;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
import com.google.android.gms.vision.Frame;
|
import com.google.android.gms.vision.Frame;
|
||||||
import com.google.android.gms.vision.barcode.Barcode;
|
import com.google.android.gms.vision.barcode.Barcode;
|
||||||
import com.google.android.gms.vision.barcode.BarcodeDetector;
|
import com.google.android.gms.vision.barcode.BarcodeDetector;
|
||||||
import de.overview.wg.its.mispbump.QrSyncActivity;
|
|
||||||
import de.overview.wg.its.mispbump.R;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -47,19 +62,79 @@ import java.util.List;
|
||||||
import java.util.concurrent.Semaphore;
|
import java.util.concurrent.Semaphore;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import lu.circl.mispbump.R;
|
||||||
|
|
||||||
public class CameraFragment extends Fragment implements ActivityCompat.OnRequestPermissionsResultCallback {
|
public class CameraFragment extends Fragment implements ActivityCompat.OnRequestPermissionsResultCallback {
|
||||||
|
|
||||||
private QrSyncActivity parentActivity;
|
private class ImageProcessingThread extends Thread {
|
||||||
|
|
||||||
|
private boolean isRunning = true;
|
||||||
|
private int lastAccessedIndex = 0;
|
||||||
|
private Bitmap[] processQueue = new Bitmap[10];
|
||||||
|
|
||||||
|
ImageProcessingThread() {
|
||||||
|
Log.i(TAG, "Image worker thread created");
|
||||||
|
}
|
||||||
|
|
||||||
|
void addToQueue(Bitmap bitmap) {
|
||||||
|
processQueue[lastAccessedIndex] = bitmap;
|
||||||
|
// circular array access
|
||||||
|
lastAccessedIndex = (lastAccessedIndex + 1) % processQueue.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while (isRunning) {
|
||||||
|
|
||||||
|
// no need to process further images
|
||||||
|
if (!readQrEnabled) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < processQueue.length; i++) {
|
||||||
|
|
||||||
|
// queue position already processed or not in use
|
||||||
|
if (processQueue[i] == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// analyze image for qr codes
|
||||||
|
SparseArray<Barcode> barcodes = barcodeDetector.detect(
|
||||||
|
new Frame.Builder().setBitmap(processQueue[i]).build()
|
||||||
|
);
|
||||||
|
|
||||||
|
// does the frame contain any qr code?
|
||||||
|
if (barcodes.size() > 0) {
|
||||||
|
if (readQrEnabled) {
|
||||||
|
qrResultCallback.qrScanResult(barcodes.valueAt(0).rawValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set buffer entry as processed
|
||||||
|
processQueue[i] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sleep between analysis of buffer (-25% cpu usage)
|
||||||
|
try {
|
||||||
|
sleep(250);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String TAG = "CAMERA";
|
||||||
|
|
||||||
|
private View hideCamView;
|
||||||
|
|
||||||
|
private QrScanCallback qrResultCallback;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Context context) {
|
public void onAttach(Context context) {
|
||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
parentActivity = (QrSyncActivity) context;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Conversion from screen rotation to JPEG orientation.
|
|
||||||
*/
|
|
||||||
private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
|
private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
|
||||||
private static final int REQUEST_CAMERA_PERMISSION = 1;
|
private static final int REQUEST_CAMERA_PERMISSION = 1;
|
||||||
private static final String FRAGMENT_DIALOG = "dialog";
|
private static final String FRAGMENT_DIALOG = "dialog";
|
||||||
|
@ -71,11 +146,6 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest
|
||||||
ORIENTATIONS.append(Surface.ROTATION_270, 180);
|
ORIENTATIONS.append(Surface.ROTATION_270, 180);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Tag for the {@link Log}.
|
|
||||||
*/
|
|
||||||
private static final String TAG = "MISP_LOG";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Max preview width that is guaranteed by Camera2 API
|
* Max preview width that is guaranteed by Camera2 API
|
||||||
*/
|
*/
|
||||||
|
@ -92,8 +162,11 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest
|
||||||
*/
|
*/
|
||||||
private final TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {
|
private final TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {
|
||||||
|
|
||||||
|
boolean processing = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
|
public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
|
||||||
|
Log.i(TAG, "Width: " + width + "; height: " + height);
|
||||||
openCamera(width, height);
|
openCamera(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,9 +183,10 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest
|
||||||
@Override
|
@Override
|
||||||
public void onSurfaceTextureUpdated(SurfaceTexture texture) {
|
public void onSurfaceTextureUpdated(SurfaceTexture texture) {
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private ImageProcessingThread imageProcessingThread;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ID of the current {@link CameraDevice}.
|
* ID of the current {@link CameraDevice}.
|
||||||
*/
|
*/
|
||||||
|
@ -121,12 +195,12 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest
|
||||||
/**
|
/**
|
||||||
* An {@link AutoFitTextureView} for camera preview.
|
* An {@link AutoFitTextureView} for camera preview.
|
||||||
*/
|
*/
|
||||||
private AutoFitTextureView mTextureView;
|
private AutoFitTextureView autoFitTextureView;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link CameraCaptureSession } for camera preview.
|
* A {@link CameraCaptureSession } for camera preview.
|
||||||
*/
|
*/
|
||||||
private CameraCaptureSession mCaptureSession;
|
private CameraCaptureSession previewCaptureSession;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A reference to the opened {@link CameraDevice}.
|
* A reference to the opened {@link CameraDevice}.
|
||||||
|
@ -182,36 +256,22 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest
|
||||||
private Handler mBackgroundHandler;
|
private Handler mBackgroundHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An {@link ImageReader} that handles still image capture.
|
* An {@link ImageReader} that handles still bitmap capture.
|
||||||
*/
|
*/
|
||||||
private ImageReader mImageReader;
|
private ImageReader stillImageReader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a
|
* This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a
|
||||||
* still image is ready to be saved.
|
* still bitmap is ready to be saved.
|
||||||
*/
|
*/
|
||||||
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {
|
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onImageAvailable(ImageReader reader) {
|
public void onImageAvailable(ImageReader reader) {
|
||||||
|
|
||||||
Image image = reader.acquireNextImage();
|
Image image = reader.acquireNextImage();
|
||||||
Bitmap bitmap = YUV2Bitmap(image);
|
Bitmap bitmap = YUV2Bitmap(image);
|
||||||
|
imageProcessingThread.addToQueue(bitmap);
|
||||||
if (bitmap != null && readQrEnabled) {
|
image.close();
|
||||||
|
|
||||||
Frame frame = new Frame.Builder().setBitmap(bitmap).build();
|
|
||||||
SparseArray<Barcode> barcodes = barcodeDetector.detect(frame);
|
|
||||||
|
|
||||||
if (barcodes.size() > 0) {
|
|
||||||
parentActivity.onReadQrCode(barcodes.valueAt(0).rawValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (image != null) {
|
|
||||||
image.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -266,10 +326,12 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest
|
||||||
|
|
||||||
// Collect the supported resolutions that are at least as big as the preview Surface
|
// Collect the supported resolutions that are at least as big as the preview Surface
|
||||||
List<Size> bigEnough = new ArrayList<>();
|
List<Size> bigEnough = new ArrayList<>();
|
||||||
|
|
||||||
// Collect the supported resolutions that are smaller than the preview Surface
|
// Collect the supported resolutions that are smaller than the preview Surface
|
||||||
List<Size> notBigEnough = new ArrayList<>();
|
List<Size> notBigEnough = new ArrayList<>();
|
||||||
int w = aspectRatio.getWidth();
|
int w = aspectRatio.getWidth();
|
||||||
int h = aspectRatio.getHeight();
|
int h = aspectRatio.getHeight();
|
||||||
|
|
||||||
for (Size option : choices) {
|
for (Size option : choices) {
|
||||||
if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight && option.getHeight() == option.getWidth() * h / w) {
|
if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight && option.getHeight() == option.getWidth() * h / w) {
|
||||||
if (option.getWidth() >= textureViewWidth && option.getHeight() >= textureViewHeight) {
|
if (option.getWidth() >= textureViewWidth && option.getHeight() >= textureViewHeight) {
|
||||||
|
@ -294,19 +356,19 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
|
||||||
View v = inflater.inflate(R.layout.fragment_camera, container, false);
|
View v = inflater.inflate(R.layout.fragment_camera, container, false);
|
||||||
|
|
||||||
|
hideCamView = v.findViewById(R.id.hideCam);
|
||||||
|
hideCamView.setVisibility(View.GONE);
|
||||||
|
|
||||||
initRenderScript();
|
initRenderScript();
|
||||||
|
|
||||||
setUpBarcodeDetector();
|
setUpBarcodeDetector();
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(final View view, Bundle savedInstanceState) {
|
public void onViewCreated(final View view, Bundle savedInstanceState) {
|
||||||
mTextureView = view.findViewById(R.id.texture);
|
autoFitTextureView = view.findViewById(R.id.texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -317,23 +379,32 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
startBackgroundThread();
|
enablePreview();
|
||||||
|
// startBackgroundThread();
|
||||||
// When the screen is turned off and turned back on, the SurfaceTexture is already
|
//
|
||||||
// available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open
|
// imageProcessingThread = new ImageProcessingThread();
|
||||||
// a camera and start preview from here (otherwise, we wait until the surface is ready in
|
// imageProcessingThread.start();
|
||||||
// the SurfaceTextureListener).
|
//
|
||||||
if (mTextureView.isAvailable()) {
|
// // When the screen is turned off and turned back on, the SurfaceTexture is already
|
||||||
openCamera(mTextureView.getWidth(), mTextureView.getHeight());
|
// // available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open
|
||||||
} else {
|
// // a camera and start preview from here (otherwise, we wait until the surface is ready in
|
||||||
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
|
// // the SurfaceTextureListener).
|
||||||
}
|
// if (autoFitTextureView.isAvailable()) {
|
||||||
|
// openCamera(autoFitTextureView.getWidth(), autoFitTextureView.getHeight());
|
||||||
|
// } else {
|
||||||
|
// autoFitTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPause() {
|
public void onPause() {
|
||||||
closeCamera();
|
closeCamera();
|
||||||
stopBackgroundThread();
|
stopBackgroundThread();
|
||||||
|
|
||||||
|
if (imageProcessingThread.isAlive()) {
|
||||||
|
imageProcessingThread.isRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
super.onPause();
|
super.onPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -365,31 +436,35 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest
|
||||||
@SuppressWarnings("SuspiciousNameCombination")
|
@SuppressWarnings("SuspiciousNameCombination")
|
||||||
private void setUpCameraOutputs(int width, int height) {
|
private void setUpCameraOutputs(int width, int height) {
|
||||||
Activity activity = getActivity();
|
Activity activity = getActivity();
|
||||||
|
assert activity != null;
|
||||||
CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
|
CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (String cameraId : manager.getCameraIdList()) {
|
for (String cameraId : manager.getCameraIdList()) {
|
||||||
|
|
||||||
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
|
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
|
||||||
|
|
||||||
// We don't use a front facing camera in this sample.
|
|
||||||
Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
|
Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
|
||||||
|
|
||||||
if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
|
if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
|
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
|
||||||
|
|
||||||
if (map == null) {
|
if (map == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For still image captures, we use the largest available size.
|
// For still bitmap captures, we use the largest available size.
|
||||||
Size largest = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.YUV_420_888)), new CompareSizesByArea());
|
Size largest = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.YUV_420_888)), new CompareSizesByArea());
|
||||||
|
|
||||||
mImageReader = ImageReader.newInstance(largest.getWidth() / 8, largest.getHeight() / 8, ImageFormat.YUV_420_888, 2);
|
stillImageReader = ImageReader.newInstance(largest.getWidth() / 8, largest.getHeight() / 8, ImageFormat.YUV_420_888, 2);
|
||||||
mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);
|
stillImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);
|
||||||
|
|
||||||
// Find out if we need to swap dimension to get the preview size relative to sensor coordinate.
|
// Find out if we need to swap dimension to get the preview size relative to sensor coordinate.
|
||||||
int displayRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
|
int displayRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
|
||||||
|
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
int mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
|
int mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
|
||||||
boolean swappedDimensions = false;
|
boolean swappedDimensions = false;
|
||||||
|
@ -433,6 +508,12 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest
|
||||||
maxPreviewHeight = MAX_PREVIEW_HEIGHT;
|
maxPreviewHeight = MAX_PREVIEW_HEIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Size[] sizes = map.getOutputSizes(SurfaceTexture.class);
|
||||||
|
|
||||||
|
for (Size size : sizes) {
|
||||||
|
Log.i(TAG, size.toString());
|
||||||
|
}
|
||||||
|
|
||||||
// Danger, W.R.! Attempting to use too large a preview size could exceed the camera
|
// Danger, W.R.! Attempting to use too large a preview size could exceed the camera
|
||||||
// bus' bandwidth limitation, resulting in gorgeous previews but the storage of
|
// bus' bandwidth limitation, resulting in gorgeous previews but the storage of
|
||||||
// garbage capture data.
|
// garbage capture data.
|
||||||
|
@ -442,10 +523,11 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest
|
||||||
|
|
||||||
// We fit the aspect ratio of TextureView to the size of preview we picked.
|
// We fit the aspect ratio of TextureView to the size of preview we picked.
|
||||||
int orientation = getResources().getConfiguration().orientation;
|
int orientation = getResources().getConfiguration().orientation;
|
||||||
|
|
||||||
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
mTextureView.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());
|
autoFitTextureView.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());
|
||||||
} else {
|
} else {
|
||||||
mTextureView.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());
|
autoFitTextureView.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());
|
||||||
}
|
}
|
||||||
|
|
||||||
mCameraId = cameraId;
|
mCameraId = cameraId;
|
||||||
|
@ -464,8 +546,9 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest
|
||||||
* Opens the camera specified by {@link CameraFragment#mCameraId}.
|
* Opens the camera specified by {@link CameraFragment#mCameraId}.
|
||||||
*/
|
*/
|
||||||
private void openCamera(int width, int height) {
|
private void openCamera(int width, int height) {
|
||||||
|
Activity activity = getActivity();
|
||||||
|
|
||||||
if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
|
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
|
||||||
requestCameraPermission();
|
requestCameraPermission();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -473,7 +556,6 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest
|
||||||
setUpCameraOutputs(width, height);
|
setUpCameraOutputs(width, height);
|
||||||
configureTransform(width, height);
|
configureTransform(width, height);
|
||||||
|
|
||||||
Activity activity = getActivity();
|
|
||||||
CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
|
CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -497,9 +579,9 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest
|
||||||
try {
|
try {
|
||||||
mCameraOpenCloseLock.acquire();
|
mCameraOpenCloseLock.acquire();
|
||||||
|
|
||||||
if (null != mCaptureSession) {
|
if (null != previewCaptureSession) {
|
||||||
mCaptureSession.close();
|
previewCaptureSession.close();
|
||||||
mCaptureSession = null;
|
previewCaptureSession = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null != mCameraDevice) {
|
if (null != mCameraDevice) {
|
||||||
|
@ -507,9 +589,9 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest
|
||||||
mCameraDevice = null;
|
mCameraDevice = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null != mImageReader) {
|
if (null != stillImageReader) {
|
||||||
mImageReader.close();
|
stillImageReader.close();
|
||||||
mImageReader = null;
|
stillImageReader = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
|
@ -532,8 +614,13 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest
|
||||||
* Stops the background thread and its {@link Handler}.
|
* Stops the background thread and its {@link Handler}.
|
||||||
*/
|
*/
|
||||||
private void stopBackgroundThread() {
|
private void stopBackgroundThread() {
|
||||||
mBackgroundThread.quitSafely();
|
|
||||||
|
if (mBackgroundThread == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
mBackgroundThread.quitSafely();
|
||||||
mBackgroundThread.join();
|
mBackgroundThread.join();
|
||||||
mBackgroundThread = null;
|
mBackgroundThread = null;
|
||||||
mBackgroundHandler = null;
|
mBackgroundHandler = null;
|
||||||
|
@ -547,23 +634,23 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest
|
||||||
*/
|
*/
|
||||||
private void createCameraPreviewSession() {
|
private void createCameraPreviewSession() {
|
||||||
try {
|
try {
|
||||||
SurfaceTexture texture = mTextureView.getSurfaceTexture();
|
// from AutoFitTextureView
|
||||||
assert texture != null;
|
SurfaceTexture texture = autoFitTextureView.getSurfaceTexture();
|
||||||
|
|
||||||
// We configure the size of default buffer to be the size of camera preview we want.
|
// We configure the size of default buffer to be the size of camera preview we want.
|
||||||
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
|
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
|
||||||
|
|
||||||
// This is the output Surface we need to start preview.
|
// This is the output Surface we need to start preview.
|
||||||
Surface surface = new Surface(texture);
|
Surface surface = new Surface(texture);
|
||||||
Surface mImageSurface = mImageReader.getSurface();
|
Surface mImageSurface = stillImageReader.getSurface();
|
||||||
|
|
||||||
// We set up a CaptureRequest.Builder with the output Surface.
|
// We set up a CaptureRequest.Builder with the output Surface.
|
||||||
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
|
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); // TEMPLATE_ZERO_SHUTTER_LAG
|
||||||
mPreviewRequestBuilder.addTarget(surface);
|
mPreviewRequestBuilder.addTarget(surface);
|
||||||
mPreviewRequestBuilder.addTarget(mImageSurface);
|
mPreviewRequestBuilder.addTarget(mImageSurface);
|
||||||
|
|
||||||
// Here, we create a CameraCaptureSession for camera preview.
|
// Here, we create a CameraCaptureSession for camera preview.
|
||||||
mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
|
mCameraDevice.createCaptureSession(Arrays.asList(surface, stillImageReader.getSurface()),
|
||||||
new CameraCaptureSession.StateCallback() {
|
new CameraCaptureSession.StateCallback() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -574,14 +661,14 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
// When the session is ready, we start displaying the preview.
|
// When the session is ready, we start displaying the preview.
|
||||||
mCaptureSession = cameraCaptureSession;
|
previewCaptureSession = cameraCaptureSession;
|
||||||
try {
|
try {
|
||||||
// Auto focus should be continuous for camera preview.
|
// Auto focus should be continuous for camera preview.
|
||||||
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
|
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); // CONTROL_AF_MODE_CONTINUOUS_PICTURE
|
||||||
|
|
||||||
// Finally, we start displaying the camera preview.
|
// Finally, we start displaying the camera preview.
|
||||||
mPreviewRequest = mPreviewRequestBuilder.build();
|
mPreviewRequest = mPreviewRequestBuilder.build();
|
||||||
mCaptureSession.setRepeatingRequest(mPreviewRequest, null, mBackgroundHandler);
|
previewCaptureSession.setRepeatingRequest(mPreviewRequest, null, mBackgroundHandler);
|
||||||
} catch (CameraAccessException e) {
|
} catch (CameraAccessException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
@ -599,26 +686,28 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures the necessary {@link android.graphics.Matrix} transformation to `mTextureView`.
|
* Configures the necessary {@link android.graphics.Matrix} transformation to `autoFitTextureView`.
|
||||||
* This method should be called after the camera preview size is determined in
|
* This method should be called after the camera preview size is determined in
|
||||||
* setUpCameraOutputs and also the size of `mTextureView` is fixed.
|
* setUpCameraOutputs and also the size of `autoFitTextureView` is fixed.
|
||||||
*
|
*
|
||||||
* @param viewWidth The width of `mTextureView`
|
* @param viewWidth The width of `autoFitTextureView`
|
||||||
* @param viewHeight The height of `mTextureView`
|
* @param viewHeight The height of `autoFitTextureView`
|
||||||
*/
|
*/
|
||||||
private void configureTransform(int viewWidth, int viewHeight) {
|
private void configureTransform(int viewWidth, int viewHeight) {
|
||||||
Activity activity = getActivity();
|
Activity activity = getActivity();
|
||||||
|
|
||||||
if (null == mTextureView || null == mPreviewSize || null == activity) {
|
if (null == autoFitTextureView || null == mPreviewSize || null == activity) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
|
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
|
||||||
|
|
||||||
Matrix matrix = new Matrix();
|
Matrix matrix = new Matrix();
|
||||||
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
|
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
|
||||||
RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
|
RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
|
||||||
float centerX = viewRect.centerX();
|
float centerX = viewRect.centerX();
|
||||||
float centerY = viewRect.centerY();
|
float centerY = viewRect.centerY();
|
||||||
|
|
||||||
if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
|
if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
|
||||||
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
|
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
|
||||||
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
|
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
|
||||||
|
@ -630,7 +719,7 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest
|
||||||
} else if (Surface.ROTATION_180 == rotation) {
|
} else if (Surface.ROTATION_180 == rotation) {
|
||||||
matrix.postRotate(180, centerX, centerY);
|
matrix.postRotate(180, centerX, centerY);
|
||||||
}
|
}
|
||||||
mTextureView.setTransform(matrix);
|
autoFitTextureView.setTransform(matrix);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -646,7 +735,6 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows an error message dialog.
|
* Shows an error message dialog.
|
||||||
*/
|
*/
|
||||||
|
@ -710,6 +798,15 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public interface QrScanCallback {
|
||||||
|
void qrScanResult(String qrData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface CameraReadyCallback {
|
||||||
|
void ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
private CameraReadyCallback cameraReadyCallback;
|
||||||
private boolean readQrEnabled = true;
|
private boolean readQrEnabled = true;
|
||||||
private BarcodeDetector barcodeDetector;
|
private BarcodeDetector barcodeDetector;
|
||||||
private RenderScript renderScript;
|
private RenderScript renderScript;
|
||||||
|
@ -763,12 +860,58 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setReadQrEnabled(boolean enabled) {
|
public void setReadQrEnabled(boolean enabled) {
|
||||||
|
|
||||||
Log.d(TAG, "setReadQrEnabled() called with: enabled = [" + enabled + "]");
|
|
||||||
|
|
||||||
readQrEnabled = enabled;
|
readQrEnabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void disablePreview() {
|
||||||
|
hideCamView.setAlpha(0f);
|
||||||
|
hideCamView.setVisibility(View.VISIBLE);
|
||||||
|
hideCamView.animate()
|
||||||
|
.alpha(1f)
|
||||||
|
.setDuration(250)
|
||||||
|
.setListener(new AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
closeCamera();
|
||||||
|
stopBackgroundThread();
|
||||||
|
|
||||||
|
if (imageProcessingThread.isAlive()) {
|
||||||
|
imageProcessingThread.isRunning = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enablePreview() {
|
||||||
|
|
||||||
|
startBackgroundThread();
|
||||||
|
|
||||||
|
imageProcessingThread = new ImageProcessingThread();
|
||||||
|
imageProcessingThread.start();
|
||||||
|
|
||||||
|
if (autoFitTextureView.isAvailable()) {
|
||||||
|
openCamera(autoFitTextureView.getWidth(), autoFitTextureView.getHeight());
|
||||||
|
} else {
|
||||||
|
autoFitTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
hideCamView.setAlpha(1f);
|
||||||
|
hideCamView.setVisibility(View.VISIBLE);
|
||||||
|
hideCamView.animate()
|
||||||
|
.alpha(0f)
|
||||||
|
.setStartDelay(100)
|
||||||
|
.setDuration(1000)
|
||||||
|
.setListener(new AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
hideCamView.setVisibility(View.GONE);
|
||||||
|
if (cameraReadyCallback != null) {
|
||||||
|
cameraReadyCallback.ready();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void setUpBarcodeDetector() {
|
private void setUpBarcodeDetector() {
|
||||||
barcodeDetector = new BarcodeDetector.Builder(getActivity())
|
barcodeDetector = new BarcodeDetector.Builder(getActivity())
|
||||||
.setBarcodeFormats(Barcode.QR_CODE)
|
.setBarcodeFormats(Barcode.QR_CODE)
|
||||||
|
@ -778,4 +921,13 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest
|
||||||
Toast.makeText(getActivity(), "Could not setup QR-Code scanner!", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getActivity(), "Could not setup QR-Code scanner!", Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public void setOnQrAvailableListener(QrScanCallback callback) {
|
||||||
|
qrResultCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCameraReadyCallback(CameraReadyCallback callback) {
|
||||||
|
this.cameraReadyCallback = callback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
package lu.circl.mispbump.custom_views;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||||
|
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
||||||
|
|
||||||
|
public class ExtendedBottomSheetBehavior<V extends View> extends BottomSheetBehavior<V> {
|
||||||
|
|
||||||
|
private boolean swipeable = false;
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
public ExtendedBottomSheetBehavior() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExtendedBottomSheetBehavior(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
|
||||||
|
if (swipeable) {
|
||||||
|
return super.onInterceptTouchEvent(parent, child, event);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
|
||||||
|
if (swipeable) {
|
||||||
|
return super.onTouchEvent(parent, child, event);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onNestedPreFling(CoordinatorLayout parent, V child, View target, float velocityX, float velocityY) {
|
||||||
|
if (swipeable) {
|
||||||
|
return super.onNestedPreFling(parent, child, target, velocityX, velocityY);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSwipeable(boolean swipeable) {
|
||||||
|
this.swipeable = swipeable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSwipeable() {
|
||||||
|
return swipeable;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package lu.circl.mispbump.custom_views;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import lu.circl.mispbump.R;
|
||||||
|
|
||||||
|
public class MaterialPreferenceText extends ConstraintLayout {
|
||||||
|
|
||||||
|
private View baseView;
|
||||||
|
private TextView title, subtitle;
|
||||||
|
|
||||||
|
public MaterialPreferenceText(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
|
||||||
|
LayoutInflater inflater = LayoutInflater.from(context);
|
||||||
|
baseView = inflater.inflate(R.layout.material_preference_text, this);
|
||||||
|
|
||||||
|
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.MaterialPreferenceText);
|
||||||
|
|
||||||
|
ImageView icon = baseView.findViewById(R.id.material_preference_src);
|
||||||
|
icon.setImageResource(a.getResourceId(R.styleable.MaterialPreferenceText_pref_icon, 0x0));
|
||||||
|
|
||||||
|
title = baseView.findViewById(R.id.material_preference_title);
|
||||||
|
title.setText(a.getString(R.styleable.MaterialPreferenceText_text));
|
||||||
|
|
||||||
|
subtitle = baseView.findViewById(R.id.material_preference_subtitle);
|
||||||
|
subtitle.setText(a.getString(R.styleable.MaterialPreferenceText_subText));
|
||||||
|
|
||||||
|
a.recycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSubText(String subText) {
|
||||||
|
subtitle.setText(subText);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
package lu.circl.mispbump.custom_views;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.ColorStateList;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.appcompat.widget.LinearLayoutCompat;
|
||||||
|
import androidx.core.widget.ImageViewCompat;
|
||||||
|
|
||||||
|
import lu.circl.mispbump.R;
|
||||||
|
|
||||||
|
public class UploadAction extends LinearLayoutCompat {
|
||||||
|
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
public enum UploadState {
|
||||||
|
PENDING,
|
||||||
|
LOADING,
|
||||||
|
DONE,
|
||||||
|
ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextView errorView;
|
||||||
|
private UploadState currentUploadState;
|
||||||
|
private ImageView stateView;
|
||||||
|
private ProgressBar progressBar;
|
||||||
|
|
||||||
|
|
||||||
|
public UploadAction(Context context) {
|
||||||
|
super(context);
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UploadAction(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
|
||||||
|
this.context = context;
|
||||||
|
|
||||||
|
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.UploadAction);
|
||||||
|
String title = a.getString(R.styleable.UploadAction_title);
|
||||||
|
a.recycle();
|
||||||
|
|
||||||
|
LayoutInflater inflater = LayoutInflater.from(context);
|
||||||
|
View baseView = inflater.inflate(R.layout.view_upload_action, this);
|
||||||
|
|
||||||
|
errorView = findViewById(R.id.error);
|
||||||
|
|
||||||
|
TextView titleView = baseView.findViewById(R.id.title);
|
||||||
|
titleView.setText(title);
|
||||||
|
|
||||||
|
stateView = findViewById(R.id.stateView);
|
||||||
|
progressBar = findViewById(R.id.progressBar);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays an error message for the upload action.
|
||||||
|
* @param error a string to show or null to hide
|
||||||
|
*/
|
||||||
|
public void setError(String error) {
|
||||||
|
if (error == null) {
|
||||||
|
errorView.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
errorView.setText(error);
|
||||||
|
errorView.setVisibility(VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentUploadState(UploadState state) {
|
||||||
|
|
||||||
|
currentUploadState = state;
|
||||||
|
progressBar.setVisibility(GONE);
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case PENDING:
|
||||||
|
stateView.setVisibility(VISIBLE);
|
||||||
|
stateView.setImageResource(R.drawable.ic_info_outline);
|
||||||
|
ImageViewCompat.setImageTintList(stateView, ColorStateList.valueOf(context.getColor(R.color.status_amber)));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LOADING:
|
||||||
|
stateView.setVisibility(GONE);
|
||||||
|
progressBar.setVisibility(VISIBLE);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DONE:
|
||||||
|
stateView.setVisibility(VISIBLE);
|
||||||
|
stateView.setImageResource(R.drawable.ic_check);
|
||||||
|
ImageViewCompat.setImageTintList(stateView, ColorStateList.valueOf(context.getColor(R.color.status_green)));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ERROR:
|
||||||
|
stateView.setVisibility(VISIBLE);
|
||||||
|
stateView.setImageResource(R.drawable.ic_error_outline);
|
||||||
|
ImageViewCompat.setImageTintList(stateView, ColorStateList.valueOf(context.getColor(R.color.status_red)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public UploadState getCurrentUploadState() {
|
||||||
|
return currentUploadState;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package lu.circl.mispbump.fragments;
|
||||||
|
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
|
public class HomeFragment extends Fragment {
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package lu.circl.mispbump.fragments;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Switch;
|
||||||
|
|
||||||
|
import lu.circl.mispbump.R;
|
||||||
|
|
||||||
|
public class SyncOptionsFragment extends Fragment {
|
||||||
|
|
||||||
|
public Switch allowSelfSigned, push, pull, cache;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View v = inflater.inflate(R.layout.fragment_sync_options, container, false);
|
||||||
|
|
||||||
|
allowSelfSigned = v.findViewById(R.id.self_signed_switch);
|
||||||
|
push = v.findViewById(R.id.push_switch);
|
||||||
|
pull = v.findViewById(R.id.pull_switch);
|
||||||
|
cache = v.findViewById(R.id.cache_switch);
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package lu.circl.mispbump.interfaces;
|
||||||
|
|
||||||
|
public interface IOnItemClickListener<T> {
|
||||||
|
void onItemClick(T clickedObject);
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package lu.circl.mispbump.models;
|
||||||
|
|
||||||
|
import lu.circl.mispbump.restful_client.Organisation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Class that holds the information needed synchronize two misp instances.
|
||||||
|
* This class can be serialized and passed via QR code.
|
||||||
|
*/
|
||||||
|
public class SyncInformation {
|
||||||
|
|
||||||
|
public Organisation organisation;
|
||||||
|
public String syncUserEmail;
|
||||||
|
public String syncUserPassword;
|
||||||
|
public String syncUserAuthkey;
|
||||||
|
public String baseUrl;
|
||||||
|
|
||||||
|
public SyncInformation() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "SyncInformation{" +
|
||||||
|
"organisation=" + organisation +
|
||||||
|
", syncUserEmail='" + syncUserEmail + '\'' +
|
||||||
|
", syncUserPassword='" + syncUserPassword + '\'' +
|
||||||
|
", syncUserAuthkey='" + syncUserAuthkey + '\'' +
|
||||||
|
", baseUrl='" + baseUrl + '\'' +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
package lu.circl.mispbump.models;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class UploadInformation implements Serializable {
|
||||||
|
|
||||||
|
public enum SyncStatus {
|
||||||
|
COMPLETE,
|
||||||
|
FAILURE,
|
||||||
|
PENDING
|
||||||
|
}
|
||||||
|
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
private SyncStatus currentSyncStatus = SyncStatus.PENDING;
|
||||||
|
|
||||||
|
private boolean allowSelfSigned, pull, push, cached;
|
||||||
|
|
||||||
|
private SyncInformation local;
|
||||||
|
private SyncInformation remote;
|
||||||
|
|
||||||
|
private Date date;
|
||||||
|
|
||||||
|
public UploadInformation() {
|
||||||
|
this(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UploadInformation(SyncInformation local) {
|
||||||
|
this(local, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UploadInformation(SyncInformation local, SyncInformation remote) {
|
||||||
|
id = UUID.randomUUID();
|
||||||
|
date = Calendar.getInstance().getTime();
|
||||||
|
|
||||||
|
this.local = local;
|
||||||
|
this.remote = remote;
|
||||||
|
}
|
||||||
|
|
||||||
|
// getter and setter
|
||||||
|
|
||||||
|
public void setCurrentSyncStatus(SyncStatus status) {
|
||||||
|
currentSyncStatus = status;
|
||||||
|
}
|
||||||
|
public SyncStatus getCurrentSyncStatus() {
|
||||||
|
return currentSyncStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocal(SyncInformation local) {
|
||||||
|
this.local = local;
|
||||||
|
}
|
||||||
|
public SyncInformation getLocal() {
|
||||||
|
return local;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRemote(SyncInformation remote) {
|
||||||
|
this.remote = remote;
|
||||||
|
}
|
||||||
|
public SyncInformation getRemote() {
|
||||||
|
return remote;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
public void setId(UUID id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDate() {
|
||||||
|
setDate(Calendar.getInstance().getTime());
|
||||||
|
}
|
||||||
|
public void setDate(Date date) {
|
||||||
|
this.date = date;
|
||||||
|
}
|
||||||
|
public Date getDate() {
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
public String getDateString() {
|
||||||
|
SimpleDateFormat df = new SimpleDateFormat("dd.mm.yyyy", Locale.getDefault());
|
||||||
|
return df.format(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAllowSelfSigned() {
|
||||||
|
return allowSelfSigned;
|
||||||
|
}
|
||||||
|
public void setAllowSelfSigned(boolean allowSelfSigned) {
|
||||||
|
this.allowSelfSigned = allowSelfSigned;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPull() {
|
||||||
|
return pull;
|
||||||
|
}
|
||||||
|
public void setPull(boolean pull) {
|
||||||
|
this.pull = pull;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPush() {
|
||||||
|
return push;
|
||||||
|
}
|
||||||
|
public void setPush(boolean push) {
|
||||||
|
this.push = push;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCached() {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
public void setCached(boolean cached) {
|
||||||
|
this.cached = cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "UploadInformation{" +
|
||||||
|
"currentSyncStatus=" + currentSyncStatus +
|
||||||
|
", local=" + local.toString() +
|
||||||
|
", remote=" + remote.toString() +
|
||||||
|
", date=" + date +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package lu.circl.mispbump.restful_client;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.Expose;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
public class MispOrganisation {
|
||||||
|
@SerializedName("Organisation")
|
||||||
|
@Expose
|
||||||
|
public Organisation organisation;
|
||||||
|
}
|
|
@ -0,0 +1,526 @@
|
||||||
|
package lu.circl.mispbump.restful_client;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.NoRouteToHostException;
|
||||||
|
import java.security.cert.CertPathValidatorException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.net.ssl.HostnameVerifier;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLHandshakeException;
|
||||||
|
import javax.net.ssl.SSLSession;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
|
||||||
|
import lu.circl.mispbump.auxiliary.PreferenceManager;
|
||||||
|
import okhttp3.Interceptor;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.logging.HttpLoggingInterceptor;
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.Callback;
|
||||||
|
import retrofit2.Response;
|
||||||
|
import retrofit2.Retrofit;
|
||||||
|
import retrofit2.converter.gson.GsonConverterFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of the RetroFit2 Misp client.
|
||||||
|
* In order to conveniently use this api some wrapper interfaces are implemented to return the requested API endpoint as java object.
|
||||||
|
*/
|
||||||
|
public class MispRestClient {
|
||||||
|
|
||||||
|
private static final String TAG = "restClient";
|
||||||
|
|
||||||
|
public interface AvailableCallback {
|
||||||
|
void available();
|
||||||
|
|
||||||
|
void unavailable(String error);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface UserCallback {
|
||||||
|
void success(User user);
|
||||||
|
|
||||||
|
void failure(String error);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OrganisationCallback {
|
||||||
|
void success(Organisation organisation);
|
||||||
|
|
||||||
|
void failure(String error);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OrganisationsCallback {
|
||||||
|
void success(Organisation[] organisations);
|
||||||
|
void failure(String error);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ServerCallback {
|
||||||
|
void success(List<MispServer> servers);
|
||||||
|
|
||||||
|
void success(MispServer server);
|
||||||
|
|
||||||
|
void success(Server server);
|
||||||
|
|
||||||
|
void failure(String error);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private PreferenceManager preferenceManager;
|
||||||
|
private MispRestService mispRestService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the rest client to communicate with a MISP instance.
|
||||||
|
*
|
||||||
|
* @param context needed to access the preferences for loading credentials
|
||||||
|
*/
|
||||||
|
public MispRestClient(Context context) {
|
||||||
|
preferenceManager = PreferenceManager.getInstance(context);
|
||||||
|
|
||||||
|
String url = preferenceManager.getServerUrl();
|
||||||
|
|
||||||
|
Log.i(TAG, "URL: " + url);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Retrofit retrofit = new Retrofit.Builder()
|
||||||
|
.baseUrl(url)
|
||||||
|
.addConverterFactory(GsonConverterFactory.create())
|
||||||
|
.client(getUnsafeOkHttpClient())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
mispRestService = retrofit.create(MispRestService.class);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOTE: for development only!
|
||||||
|
* <p>
|
||||||
|
* Accepts all certificates including self signed.
|
||||||
|
*
|
||||||
|
* @return {@link OkHttpClient} which accepts all certificates
|
||||||
|
*/
|
||||||
|
private OkHttpClient getUnsafeOkHttpClient() {
|
||||||
|
try {
|
||||||
|
// Create a trust manager that does not validate certificate chains
|
||||||
|
final TrustManager[] trustAllCerts = new TrustManager[]{
|
||||||
|
new X509TrustManager() {
|
||||||
|
@SuppressLint("TrustAllX509TrustManager")
|
||||||
|
@Override
|
||||||
|
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("TrustAllX509TrustManager")
|
||||||
|
@Override
|
||||||
|
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
|
||||||
|
return new java.security.cert.X509Certificate[]{};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Install the all-trusting trust manager
|
||||||
|
final SSLContext sslContext = SSLContext.getInstance("SSL");
|
||||||
|
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
|
||||||
|
|
||||||
|
// Create an ssl socket factory with our all-trusting manager
|
||||||
|
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
|
||||||
|
|
||||||
|
OkHttpClient.Builder builder = new OkHttpClient.Builder();
|
||||||
|
builder.sslSocketFactory(sslSocketFactory, (X509TrustManager)trustAllCerts[0]);
|
||||||
|
builder.hostnameVerifier(new HostnameVerifier() {
|
||||||
|
@Override
|
||||||
|
public boolean verify(String hostname, SSLSession session) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// create logging interceptor
|
||||||
|
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
|
||||||
|
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
|
||||||
|
builder.addInterceptor(interceptor);
|
||||||
|
|
||||||
|
// create authorization interceptor
|
||||||
|
builder.addInterceptor(new Interceptor() {
|
||||||
|
@Override
|
||||||
|
public okhttp3.Response intercept(Chain chain) throws IOException {
|
||||||
|
Request.Builder ongoing = chain.request().newBuilder();
|
||||||
|
ongoing.addHeader("Accept", "application/json");
|
||||||
|
ongoing.addHeader("Content-Type", "application/json");
|
||||||
|
ongoing.addHeader("Authorization", preferenceManager.getAutomationKey());
|
||||||
|
return chain.proceed(ongoing.build());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// status routes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check via pyMispRoute if server is available
|
||||||
|
*
|
||||||
|
* @param callback {@link AvailableCallback}
|
||||||
|
*/
|
||||||
|
public void isAvailable(final AvailableCallback callback) {
|
||||||
|
Call<Version> call = mispRestService.pyMispVersion();
|
||||||
|
call.enqueue(new Callback<Version>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<Version> call, Response<Version> response) {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
if (response.code() == 403) {
|
||||||
|
callback.available();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback.unavailable(extractError(response));
|
||||||
|
} else {
|
||||||
|
callback.available();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<Version> call, Throwable t) {
|
||||||
|
callback.unavailable(extractError(t));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// user routes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches information about the user that is associated with saved auth key.
|
||||||
|
*
|
||||||
|
* @param callback {@link UserCallback} wrapper to return user directly
|
||||||
|
*/
|
||||||
|
public void getMyUser(final UserCallback callback) {
|
||||||
|
Call<MispUser> call = mispRestService.getMyUserInformation();
|
||||||
|
|
||||||
|
call.enqueue(new Callback<MispUser>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<MispUser> call, Response<MispUser> response) {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
callback.failure(extractError(response));
|
||||||
|
} else {
|
||||||
|
if (response.body() != null) {
|
||||||
|
callback.success(response.body().user);
|
||||||
|
} else {
|
||||||
|
callback.failure("response body was null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<MispUser> call, Throwable t) {
|
||||||
|
t.printStackTrace();
|
||||||
|
callback.failure(t.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an user with specific ID.
|
||||||
|
*
|
||||||
|
* @param userId user identifier
|
||||||
|
* @param callback {@link UserCallback} wrapper to return user directly
|
||||||
|
*/
|
||||||
|
public void getUser(int userId, final UserCallback callback) {
|
||||||
|
Call<MispUser> call = mispRestService.getUser(userId);
|
||||||
|
|
||||||
|
call.enqueue(new Callback<MispUser>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<MispUser> call, Response<MispUser> response) {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
callback.failure(extractError(response));
|
||||||
|
} else {
|
||||||
|
if (response.body() != null) {
|
||||||
|
callback.success(response.body().user);
|
||||||
|
} else {
|
||||||
|
callback.failure("response body was null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<MispUser> call, Throwable t) {
|
||||||
|
t.printStackTrace();
|
||||||
|
callback.failure(t.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a given user to the MISP instance referenced by url in preferences.
|
||||||
|
*
|
||||||
|
* @param user user to add
|
||||||
|
* @param callback {@link UserCallback} wrapper to return the created user directly
|
||||||
|
*/
|
||||||
|
public void addUser(User user, final UserCallback callback) {
|
||||||
|
Call<MispUser> call = mispRestService.addUser(user);
|
||||||
|
|
||||||
|
call.enqueue(new Callback<MispUser>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<MispUser> call, Response<MispUser> response) {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
callback.failure(extractError(response));
|
||||||
|
} else {
|
||||||
|
assert response.body() != null;
|
||||||
|
callback.success(response.body().user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<MispUser> call, Throwable t) {
|
||||||
|
callback.failure(t.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// organisation routes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an organisation by a given organisation id.
|
||||||
|
*
|
||||||
|
* @param orgId organisation identifier
|
||||||
|
* @param callback {@link OrganisationCallback} wrapper to return a organisation directly
|
||||||
|
*/
|
||||||
|
public void getOrganisation(int orgId, final OrganisationCallback callback) {
|
||||||
|
Call<MispOrganisation> call = mispRestService.getOrganisation(orgId);
|
||||||
|
|
||||||
|
call.enqueue(new Callback<MispOrganisation>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<MispOrganisation> call, Response<MispOrganisation> response) {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
callback.failure(extractError(response));
|
||||||
|
} else {
|
||||||
|
if (response.body() != null) {
|
||||||
|
callback.success(response.body().organisation);
|
||||||
|
} else {
|
||||||
|
callback.failure("Response body was nul");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<MispOrganisation> call, Throwable t) {
|
||||||
|
callback.failure(t.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Organisation[] getAllOrganisations() throws IOException {
|
||||||
|
Call<List<MispOrganisation>> call = mispRestService.getAllOrganisations();
|
||||||
|
Response<List<MispOrganisation>> response = call.execute();
|
||||||
|
|
||||||
|
List<MispOrganisation> mispOrganisations = response.body();
|
||||||
|
Organisation[] organisations = new Organisation[mispOrganisations.size()];
|
||||||
|
|
||||||
|
for (int i = 0; i < mispOrganisations.size(); i++) {
|
||||||
|
organisations[i] = mispOrganisations.get(i).organisation;
|
||||||
|
}
|
||||||
|
|
||||||
|
return organisations;
|
||||||
|
|
||||||
|
// call.enqueue(new Callback<List<MispOrganisation>>() {
|
||||||
|
// @Override
|
||||||
|
// public void onResponse(Call<List<MispOrganisation>> call, Response<List<MispOrganisation>> response) {
|
||||||
|
// if (!response.isSuccessful()) {
|
||||||
|
// // TODO handle
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// List<MispOrganisation> mispOrganisations = response.body();
|
||||||
|
//
|
||||||
|
// assert mispOrganisations != null;
|
||||||
|
//
|
||||||
|
// Organisation[] organisations = new Organisation[mispOrganisations.size()];
|
||||||
|
//
|
||||||
|
// for (int i = 0; i < mispOrganisations.size(); i++) {
|
||||||
|
// organisations[i] = mispOrganisations.get(i).organisation;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// callback.success(organisations);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Override
|
||||||
|
// public void onFailure(Call<List<MispOrganisation>> call, Throwable t) {
|
||||||
|
// callback.failure(extractError(t));
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a given organisation to the MISP instance referenced by url in preferences.
|
||||||
|
*
|
||||||
|
* @param organisation organisation to add
|
||||||
|
* @param callback {@link OrganisationCallback} wrapper to return the created organisation directly
|
||||||
|
*/
|
||||||
|
public void addOrganisation(Organisation organisation, final OrganisationCallback callback) {
|
||||||
|
Call<MispOrganisation> call = mispRestService.addOrganisation(organisation);
|
||||||
|
|
||||||
|
call.enqueue(new Callback<MispOrganisation>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<MispOrganisation> call, Response<MispOrganisation> response) {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
callback.failure(extractError(response));
|
||||||
|
} else {
|
||||||
|
assert response.body() != null;
|
||||||
|
callback.success(response.body().organisation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<MispOrganisation> call, Throwable t) {
|
||||||
|
callback.failure(t.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// server routes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all servers on MISP instance.
|
||||||
|
*
|
||||||
|
* @param callback {@link OrganisationCallback} wrapper to return a list of servers directly
|
||||||
|
*/
|
||||||
|
public void getServers(final ServerCallback callback) {
|
||||||
|
Call<List<MispServer>> call = mispRestService.getServers();
|
||||||
|
|
||||||
|
call.enqueue(new Callback<List<MispServer>>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<List<MispServer>> call, Response<List<MispServer>> response) {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
callback.failure(extractError(response));
|
||||||
|
} else {
|
||||||
|
callback.success(response.body());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<List<MispServer>> call, Throwable t) {
|
||||||
|
callback.failure(t.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a server to the MISP instance
|
||||||
|
*
|
||||||
|
* @param server the server to create
|
||||||
|
* @param callback {@link ServerCallback} wrapper to return the created server directly
|
||||||
|
*/
|
||||||
|
public void addServer(Server server, final ServerCallback callback) {
|
||||||
|
Call<Server> call = mispRestService.addServer(server);
|
||||||
|
|
||||||
|
call.enqueue(new Callback<Server>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<Server> call, Response<Server> response) {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
callback.failure(extractError(response));
|
||||||
|
} else {
|
||||||
|
callback.success(response.body());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<Server> call, Throwable t) {
|
||||||
|
callback.failure(t.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// error parsing
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts error {@link Response}s to human readable info.
|
||||||
|
* @param response erroneous response
|
||||||
|
* @param <T> type of response
|
||||||
|
* @return human readable String that describes the error
|
||||||
|
*/
|
||||||
|
private <T> String extractError(Response<T> response) {
|
||||||
|
switch (response.code()) {
|
||||||
|
// bad request (malformed)
|
||||||
|
case 400:
|
||||||
|
return "Bad request";
|
||||||
|
|
||||||
|
// unauthorized
|
||||||
|
case 401:
|
||||||
|
return "Unauthorized";
|
||||||
|
|
||||||
|
// forbidden
|
||||||
|
case 403:
|
||||||
|
try {
|
||||||
|
assert response.errorBody() != null;
|
||||||
|
JSONObject jsonError = new JSONObject(response.errorBody().string());
|
||||||
|
|
||||||
|
String name = jsonError.getString("name") + "\n";
|
||||||
|
|
||||||
|
if (name.startsWith("Authentication failed")) {
|
||||||
|
return "Authentication failed";
|
||||||
|
}
|
||||||
|
|
||||||
|
String reasons = "";
|
||||||
|
JSONObject errorReasons = jsonError.getJSONObject("errors");
|
||||||
|
|
||||||
|
Iterator<String> errorKeys = errorReasons.keys();
|
||||||
|
|
||||||
|
while (errorKeys.hasNext()) {
|
||||||
|
reasons = reasons.concat(errorReasons.getString(errorKeys.next()) + "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return name + reasons;
|
||||||
|
} catch (JSONException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Could not parse (403) error";
|
||||||
|
|
||||||
|
// not found
|
||||||
|
case 404:
|
||||||
|
return "Not found";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Unknown error";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a {@link Throwable} to a human readable error message.
|
||||||
|
* @param t throwable
|
||||||
|
* @return human readable String that describes the error.
|
||||||
|
*/
|
||||||
|
private String extractError(Throwable t) {
|
||||||
|
|
||||||
|
if (t.getCause() instanceof CertificateException) {
|
||||||
|
return "Trust anchor for certification path not found.\nSelf signed certificates are not supported.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t instanceof SSLHandshakeException) {
|
||||||
|
return "SSL Handshake Error";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t instanceof NoRouteToHostException) {
|
||||||
|
return "Server is not available (no route to host)";
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.getMessage();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
package lu.circl.mispbump.restful_client;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.http.Body;
|
||||||
|
import retrofit2.http.GET;
|
||||||
|
import retrofit2.http.POST;
|
||||||
|
import retrofit2.http.Path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RetroFit2 interface for communication with misp instances
|
||||||
|
*/
|
||||||
|
public interface MispRestService {
|
||||||
|
|
||||||
|
// settings routes
|
||||||
|
|
||||||
|
@GET("servers/getPyMISPVersion")
|
||||||
|
Call<Version> pyMispVersion();
|
||||||
|
|
||||||
|
// user routes
|
||||||
|
|
||||||
|
@GET("users/view/me")
|
||||||
|
Call<MispUser> getMyUserInformation();
|
||||||
|
|
||||||
|
@GET("users/view/{value}")
|
||||||
|
Call<MispUser> getUser(@Path("value") int userId);
|
||||||
|
|
||||||
|
@POST("admin/users/add")
|
||||||
|
Call<MispUser> addUser(@Body User user);
|
||||||
|
|
||||||
|
// organisation routes
|
||||||
|
|
||||||
|
@GET("organisations/view/{value}")
|
||||||
|
Call<MispOrganisation> getOrganisation(@Path("value") int orgId);
|
||||||
|
|
||||||
|
@GET("organisations")
|
||||||
|
Call<List<MispOrganisation>> getAllOrganisations();
|
||||||
|
|
||||||
|
@POST("admin/organisations/add")
|
||||||
|
Call<MispOrganisation> addOrganisation(@Body Organisation organisation);
|
||||||
|
|
||||||
|
// server routes
|
||||||
|
|
||||||
|
@GET("servers/index")
|
||||||
|
Call<List<MispServer>> getServers();
|
||||||
|
|
||||||
|
// @POST("servers/add")
|
||||||
|
// Call<MispServer> addServer(@Body MispServer server);
|
||||||
|
|
||||||
|
@POST("servers/add")
|
||||||
|
Call<Server> addServer(@Body Server server);
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package lu.circl.mispbump.restful_client;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import com.google.gson.annotations.Expose;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
public class MispServer {
|
||||||
|
|
||||||
|
public MispServer() {}
|
||||||
|
|
||||||
|
public MispServer(Server server, Organisation organisation, Organisation remoteOrganisation) {
|
||||||
|
this.server = server;
|
||||||
|
this.organisation = organisation;
|
||||||
|
this.remoteOrg = remoteOrganisation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SerializedName("Server")
|
||||||
|
@Expose
|
||||||
|
public Server server;
|
||||||
|
@SerializedName("Organisation")
|
||||||
|
@Expose
|
||||||
|
public Organisation organisation;
|
||||||
|
@SerializedName("RemoteOrg")
|
||||||
|
@Expose
|
||||||
|
public Organisation remoteOrg;
|
||||||
|
@SerializedName("User")
|
||||||
|
@Expose
|
||||||
|
public List<User> user;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package lu.circl.mispbump.restful_client;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.Expose;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
public class MispUser {
|
||||||
|
|
||||||
|
@SerializedName("User")
|
||||||
|
@Expose
|
||||||
|
public User user;
|
||||||
|
|
||||||
|
public MispUser(User user) {
|
||||||
|
this.user = user;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package lu.circl.mispbump.restful_client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information gathered from Misp API about a organisation.
|
||||||
|
*/
|
||||||
|
public class Organisation {
|
||||||
|
|
||||||
|
public Integer id;
|
||||||
|
public String name;
|
||||||
|
public String date_created;
|
||||||
|
public String date_modified;
|
||||||
|
public String type;
|
||||||
|
public String nationality;
|
||||||
|
public String sector;
|
||||||
|
public String contacts;
|
||||||
|
public String description;
|
||||||
|
public Boolean local;
|
||||||
|
public String uuid;
|
||||||
|
public String restricted_to_domain;
|
||||||
|
public String created_by;
|
||||||
|
public Integer user_count;
|
||||||
|
|
||||||
|
public Organisation() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Organisation(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Organisation(String name, String description) {
|
||||||
|
this.name = name;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Organisation toSyncOrganisation() {
|
||||||
|
Organisation organisation = new Organisation();
|
||||||
|
organisation.local = true;
|
||||||
|
organisation.name = name;
|
||||||
|
organisation.uuid = uuid;
|
||||||
|
organisation.description = description;
|
||||||
|
organisation.nationality = nationality;
|
||||||
|
organisation.sector = sector;
|
||||||
|
organisation.type = "Sync organisation";
|
||||||
|
organisation.contacts = contacts;
|
||||||
|
|
||||||
|
return organisation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Organisation{" +
|
||||||
|
"id=" + id +
|
||||||
|
", name='" + name + '\'' +
|
||||||
|
", date_created='" + date_created + '\'' +
|
||||||
|
", date_modified='" + date_modified + '\'' +
|
||||||
|
", type='" + type + '\'' +
|
||||||
|
", nationality='" + nationality + '\'' +
|
||||||
|
", sector='" + sector + '\'' +
|
||||||
|
", contacts='" + contacts + '\'' +
|
||||||
|
", description='" + description + '\'' +
|
||||||
|
", local=" + local +
|
||||||
|
", uuid='" + uuid + '\'' +
|
||||||
|
", restricted_to_domain='" + restricted_to_domain + '\'' +
|
||||||
|
", created_by='" + created_by + '\'' +
|
||||||
|
", user_count=" + user_count +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
package lu.circl.mispbump.restful_client;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
public class Server {
|
||||||
|
|
||||||
|
public Server() {}
|
||||||
|
|
||||||
|
public Server(String name, String url, String authkey, Integer remote_org_id) {
|
||||||
|
this.name = name;
|
||||||
|
this.url = url;
|
||||||
|
this.authkey = authkey;
|
||||||
|
this.remote_org_id = remote_org_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SerializedName("id")
|
||||||
|
public Integer id;
|
||||||
|
|
||||||
|
@SerializedName("name")
|
||||||
|
public String name;
|
||||||
|
|
||||||
|
@SerializedName("url")
|
||||||
|
public String url;
|
||||||
|
|
||||||
|
@SerializedName("authkey")
|
||||||
|
public String authkey;
|
||||||
|
|
||||||
|
@SerializedName("org_id")
|
||||||
|
public Integer org_id;
|
||||||
|
|
||||||
|
@SerializedName("push")
|
||||||
|
public Boolean push;
|
||||||
|
|
||||||
|
@SerializedName("pull")
|
||||||
|
public Boolean pull;
|
||||||
|
|
||||||
|
@SerializedName("lastpulledid")
|
||||||
|
public Object lastpulledid;
|
||||||
|
|
||||||
|
@SerializedName("lastpushedid")
|
||||||
|
public Object lastpushedid;
|
||||||
|
|
||||||
|
@SerializedName("organization")
|
||||||
|
public Object organization;
|
||||||
|
|
||||||
|
@SerializedName("remote_org_id")
|
||||||
|
public Integer remote_org_id;
|
||||||
|
|
||||||
|
@SerializedName("publish_without_email")
|
||||||
|
public Boolean publish_without_email = false;
|
||||||
|
|
||||||
|
@SerializedName("unpublish_event")
|
||||||
|
public Boolean unpublish_event;
|
||||||
|
|
||||||
|
@SerializedName("self_signed")
|
||||||
|
public Boolean self_signed = false;
|
||||||
|
|
||||||
|
@SerializedName("pull_rules")
|
||||||
|
public String pull_rules;
|
||||||
|
|
||||||
|
@SerializedName("push_rules")
|
||||||
|
public String push_rules;
|
||||||
|
|
||||||
|
@SerializedName("cert_file")
|
||||||
|
public Object cert_file;
|
||||||
|
|
||||||
|
@SerializedName("client_cert_file")
|
||||||
|
public Object client_cert_file;
|
||||||
|
|
||||||
|
@SerializedName("internal")
|
||||||
|
public Boolean internal;
|
||||||
|
|
||||||
|
@SerializedName("skip_proxy")
|
||||||
|
public Boolean skip_proxy = false;
|
||||||
|
|
||||||
|
@SerializedName("caching_enabled")
|
||||||
|
public Boolean caching_enabled;
|
||||||
|
|
||||||
|
@SerializedName("cache_timestamp")
|
||||||
|
public Boolean cache_timestamp;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Server{" +
|
||||||
|
"id=" + id +
|
||||||
|
", name='" + name + '\'' +
|
||||||
|
", url='" + url + '\'' +
|
||||||
|
", authkey='" + authkey + '\'' +
|
||||||
|
", org_id=" + org_id +
|
||||||
|
", push=" + push +
|
||||||
|
", pull=" + pull +
|
||||||
|
", lastpulledid=" + lastpulledid +
|
||||||
|
", lastpushedid=" + lastpushedid +
|
||||||
|
", organization=" + organization +
|
||||||
|
", remote_org_id=" + remote_org_id +
|
||||||
|
", publish_without_email=" + publish_without_email +
|
||||||
|
", unpublish_event=" + unpublish_event +
|
||||||
|
", self_signed=" + self_signed +
|
||||||
|
", pull_rules='" + pull_rules + '\'' +
|
||||||
|
", push_rules='" + push_rules + '\'' +
|
||||||
|
", cert_file=" + cert_file +
|
||||||
|
", client_cert_file=" + client_cert_file +
|
||||||
|
", internal=" + internal +
|
||||||
|
", skip_proxy=" + skip_proxy +
|
||||||
|
", caching_enabled=" + caching_enabled +
|
||||||
|
", cache_timestamp=" + cache_timestamp +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
package lu.circl.mispbump.restful_client;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.Expose;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
public class User {
|
||||||
|
|
||||||
|
public static final int ROLE_ADMIN = 1;
|
||||||
|
public static final int ROLE_ORG_ADMIN = 2;
|
||||||
|
public static final int ROLE_USER = 3;
|
||||||
|
public static final int ROLE_PUBLISHER = 4;
|
||||||
|
public static final int ROLE_SYNC_USER = 5;
|
||||||
|
public static final int ROLE_READ_ONLY = 6;
|
||||||
|
|
||||||
|
public User() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public User(Integer org_id, String email, Integer role_id) {
|
||||||
|
this.org_id = org_id;
|
||||||
|
this.email = email;
|
||||||
|
this.role_id = role_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public User(Integer org_id, String email, Integer role_id, String password) {
|
||||||
|
this.password = password;
|
||||||
|
this.org_id = org_id;
|
||||||
|
this.email = email;
|
||||||
|
this.role_id = role_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SerializedName("id")
|
||||||
|
@Expose
|
||||||
|
public Integer id;
|
||||||
|
@SerializedName("password")
|
||||||
|
@Expose
|
||||||
|
public String password;
|
||||||
|
@SerializedName("org_id")
|
||||||
|
@Expose
|
||||||
|
public Integer org_id;
|
||||||
|
@SerializedName("email")
|
||||||
|
@Expose
|
||||||
|
public String email;
|
||||||
|
@SerializedName("autoalert")
|
||||||
|
@Expose
|
||||||
|
public Boolean autoalert;
|
||||||
|
@SerializedName("authkey")
|
||||||
|
@Expose
|
||||||
|
public String authkey;
|
||||||
|
@SerializedName("invited_by")
|
||||||
|
@Expose
|
||||||
|
public String invited_by;
|
||||||
|
@SerializedName("gpgkey")
|
||||||
|
@Expose
|
||||||
|
public Object gpgkey;
|
||||||
|
@SerializedName("certif_public")
|
||||||
|
@Expose
|
||||||
|
public String certif_public;
|
||||||
|
@SerializedName("nids_sid")
|
||||||
|
@Expose
|
||||||
|
public String nids_sid;
|
||||||
|
@SerializedName("termsaccepted")
|
||||||
|
@Expose
|
||||||
|
public Boolean termsaccepted;
|
||||||
|
@SerializedName("newsread")
|
||||||
|
@Expose
|
||||||
|
public String newsread;
|
||||||
|
@SerializedName("role_id")
|
||||||
|
@Expose
|
||||||
|
public Integer role_id;
|
||||||
|
@SerializedName("change_pw")
|
||||||
|
@Expose
|
||||||
|
public String change_pw;
|
||||||
|
@SerializedName("contactalert")
|
||||||
|
@Expose
|
||||||
|
public Boolean contactalert;
|
||||||
|
@SerializedName("disabled")
|
||||||
|
@Expose
|
||||||
|
public Boolean disabled;
|
||||||
|
@SerializedName("expiration")
|
||||||
|
@Expose
|
||||||
|
public Object expiration;
|
||||||
|
@SerializedName("current_login")
|
||||||
|
@Expose
|
||||||
|
public String current_login;
|
||||||
|
@SerializedName("last_login")
|
||||||
|
@Expose
|
||||||
|
public String last_login;
|
||||||
|
@SerializedName("force_logout")
|
||||||
|
@Expose
|
||||||
|
public Boolean force_logout;
|
||||||
|
@SerializedName("date_created")
|
||||||
|
@Expose
|
||||||
|
public Object date_created;
|
||||||
|
@SerializedName("date_modified")
|
||||||
|
@Expose
|
||||||
|
public String date_modified;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "User{" +
|
||||||
|
"id='" + id + '\'' +
|
||||||
|
", password='" + password + '\'' +
|
||||||
|
", org_id='" + org_id + '\'' +
|
||||||
|
", email='" + email + '\'' +
|
||||||
|
", autoalert=" + autoalert +
|
||||||
|
", authkey='" + authkey + '\'' +
|
||||||
|
", invited_by='" + invited_by + '\'' +
|
||||||
|
", gpgkey=" + gpgkey +
|
||||||
|
", certif_public='" + certif_public + '\'' +
|
||||||
|
", nids_sid='" + nids_sid + '\'' +
|
||||||
|
", termsaccepted=" + termsaccepted +
|
||||||
|
", newsread='" + newsread + '\'' +
|
||||||
|
", role_id='" + role_id + '\'' +
|
||||||
|
", change_pw='" + change_pw + '\'' +
|
||||||
|
", contactalert=" + contactalert +
|
||||||
|
", disabled=" + disabled +
|
||||||
|
", expiration=" + expiration +
|
||||||
|
", current_login='" + current_login + '\'' +
|
||||||
|
", last_login='" + last_login + '\'' +
|
||||||
|
", force_logout=" + force_logout +
|
||||||
|
", date_created=" + date_created +
|
||||||
|
", date_modified='" + date_modified + '\'' +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package lu.circl.mispbump.restful_client;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
public class Version {
|
||||||
|
|
||||||
|
@SerializedName("version")
|
||||||
|
public String version;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,147 @@
|
||||||
|
package lu.circl.mispbump.security;
|
||||||
|
|
||||||
|
import android.util.Base64;
|
||||||
|
|
||||||
|
import javax.crypto.*;
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.*;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides the functionality generate a shared secret key.
|
||||||
|
* Furthermore it contains the encryption/decryption methods.
|
||||||
|
*/
|
||||||
|
public class DiffieHellman {
|
||||||
|
|
||||||
|
private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
|
||||||
|
private static final String KEY_PAIR_ALGORITHM = "EC";
|
||||||
|
private static final int KEY_SIZE = 521; // 224 | 256 | 384 | 521
|
||||||
|
private static final String KEY_AGREEMENT_ALGORITHM = "ECDH";
|
||||||
|
private static final String KEY_FACTORY_ALGORITHM = "EC";
|
||||||
|
|
||||||
|
private static DiffieHellman instance;
|
||||||
|
|
||||||
|
private PublicKey publickey;
|
||||||
|
private KeyAgreement keyAgreement;
|
||||||
|
|
||||||
|
private byte[] sharedSecret;
|
||||||
|
private IvParameterSpec ivParameterSpec;
|
||||||
|
|
||||||
|
|
||||||
|
private DiffieHellman() {
|
||||||
|
initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Singleton pattern
|
||||||
|
* @return {@link DiffieHellman}
|
||||||
|
*/
|
||||||
|
public static DiffieHellman getInstance() {
|
||||||
|
if(instance == null) {
|
||||||
|
instance = new DiffieHellman();
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a public and a private key using an elliptic curve algorithm.
|
||||||
|
* The private key is fed into the key agreement instance.
|
||||||
|
*/
|
||||||
|
private void initialize() {
|
||||||
|
try {
|
||||||
|
KeyPairGenerator kpg = KeyPairGenerator.getInstance(KEY_PAIR_ALGORITHM);
|
||||||
|
kpg.initialize(KEY_SIZE);
|
||||||
|
|
||||||
|
KeyPair kp = kpg.generateKeyPair();
|
||||||
|
publickey = kp.getPublic();
|
||||||
|
|
||||||
|
keyAgreement = KeyAgreement.getInstance(KEY_AGREEMENT_ALGORITHM);
|
||||||
|
keyAgreement.init(kp.getPrivate());
|
||||||
|
|
||||||
|
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypts data.
|
||||||
|
* @param data data to encrypt
|
||||||
|
* @return To String converted and encrypted data
|
||||||
|
*/
|
||||||
|
public String encrypt(String data) {
|
||||||
|
try {
|
||||||
|
Cipher c = Cipher.getInstance(CIPHER_ALGORITHM);
|
||||||
|
c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(sharedSecret, CIPHER_ALGORITHM), ivParameterSpec);
|
||||||
|
|
||||||
|
byte[] cipherText = c.doFinal(data.getBytes(StandardCharsets.UTF_8));
|
||||||
|
return Base64.encodeToString(cipherText, Base64.NO_WRAP);
|
||||||
|
|
||||||
|
} catch (BadPaddingException | InvalidKeyException | NoSuchPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (InvalidAlgorithmParameterException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts data with the current shared secret.
|
||||||
|
* @param data data to decrypt
|
||||||
|
* @return To String converted and decrypted data
|
||||||
|
*/
|
||||||
|
public String decrypt(String data) {
|
||||||
|
try {
|
||||||
|
Cipher c = Cipher.getInstance(CIPHER_ALGORITHM);
|
||||||
|
c.init(Cipher.DECRYPT_MODE, new SecretKeySpec(sharedSecret, CIPHER_ALGORITHM), ivParameterSpec);
|
||||||
|
|
||||||
|
byte[] cipherText = Base64.decode(data, Base64.NO_WRAP);
|
||||||
|
return new String(c.doFinal(cipherText), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
} catch (BadPaddingException | InvalidKeyException | NoSuchPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (InvalidAlgorithmParameterException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a shared secret and derives an initialisation vector from it.
|
||||||
|
* @param pk public key of the sync partner
|
||||||
|
*/
|
||||||
|
public void setForeignPublicKey(PublicKey pk) {
|
||||||
|
try {
|
||||||
|
keyAgreement.doPhase(pk, true);
|
||||||
|
|
||||||
|
byte[] tmpSharedSecret = keyAgreement.generateSecret();
|
||||||
|
sharedSecret = Arrays.copyOfRange(tmpSharedSecret, 0, 32);
|
||||||
|
|
||||||
|
byte[] inputVector = Arrays.copyOfRange(sharedSecret, 32, 48);
|
||||||
|
ivParameterSpec = new IvParameterSpec(inputVector);
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return this devices public key
|
||||||
|
*/
|
||||||
|
public PublicKey getPublicKey() {
|
||||||
|
return publickey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String publicKeyToString(PublicKey key) {
|
||||||
|
return Base64.encodeToString(key.getEncoded(), Base64.DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PublicKey publicKeyFromString(String key) throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||||
|
byte[] input = Base64.decode(key, Base64.DEFAULT);
|
||||||
|
return KeyFactory.getInstance(KEY_FACTORY_ALGORITHM).generatePublic(new X509EncodedKeySpec(input));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,273 @@
|
||||||
|
package lu.circl.mispbump.security;
|
||||||
|
|
||||||
|
import android.security.keystore.KeyGenParameterSpec;
|
||||||
|
import android.security.keystore.KeyProperties;
|
||||||
|
import android.util.Base64;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.Key;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.NoSuchProviderException;
|
||||||
|
import java.security.UnrecoverableKeyException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
|
||||||
|
import javax.crypto.BadPaddingException;
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
|
import javax.crypto.KeyGenerator;
|
||||||
|
import javax.crypto.NoSuchPaddingException;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.GCMParameterSpec;
|
||||||
|
|
||||||
|
public class KeyStoreWrapper {
|
||||||
|
|
||||||
|
private static final String TAG = "KeyStoreWrapper";
|
||||||
|
|
||||||
|
public static final String USER_INFO_ALIAS = "ALIAS_USER_INFO";
|
||||||
|
public static final String USER_ORGANISATION_INFO_ALIAS = "ALIAS_USER_ORGANISATION_INFO";
|
||||||
|
public static final String AUTOMATION_ALIAS = "ALIAS_AUTOMATION_KEY";
|
||||||
|
public static final String SERVER_URL_ALIAS = "ALIAS_SERVER_URL";
|
||||||
|
public static final String UPLOAD_INFORMATION_ALIAS = "ALIAS_UPLOAD_INFORMATION";
|
||||||
|
|
||||||
|
private static final String KEYSTORE_PROVIDER = "AndroidKeyStore";
|
||||||
|
private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
|
||||||
|
|
||||||
|
private String KEYSTORE_ALIAS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps the android key store to easily encrypt and decrypt sensitive data.
|
||||||
|
* @param alias identifies a key store entry (see public static ALIAS variables).
|
||||||
|
*/
|
||||||
|
public KeyStoreWrapper(String alias) {
|
||||||
|
KEYSTORE_ALIAS = alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return wheter an entry for this alias already exists.
|
||||||
|
*/
|
||||||
|
private boolean isInitialized() {
|
||||||
|
try {
|
||||||
|
KeyStore ks = KeyStore.getInstance(KEYSTORE_PROVIDER);
|
||||||
|
ks.load(null);
|
||||||
|
|
||||||
|
if (ks.containsAlias(KEYSTORE_ALIAS)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (KeyStoreException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (CertificateException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return SecretKey associated with the given alias.
|
||||||
|
*/
|
||||||
|
private SecretKey getStoredKey() {
|
||||||
|
try {
|
||||||
|
|
||||||
|
KeyStore ks = KeyStore.getInstance(KEYSTORE_PROVIDER);
|
||||||
|
ks.load(null);
|
||||||
|
return (SecretKey) ks.getKey(KEYSTORE_ALIAS, null);
|
||||||
|
|
||||||
|
} catch (KeyStoreException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (CertificateException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (UnrecoverableKeyException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a new key.
|
||||||
|
* @return the newly generated key.
|
||||||
|
*/
|
||||||
|
private SecretKey generateKey() {
|
||||||
|
try {
|
||||||
|
|
||||||
|
// androids key generator
|
||||||
|
final KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_PROVIDER);
|
||||||
|
|
||||||
|
// specs for the generated key
|
||||||
|
final KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(KEYSTORE_ALIAS,
|
||||||
|
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
|
||||||
|
.setKeySize(256)
|
||||||
|
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
|
||||||
|
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// initialize KeyGenerator and generate a secret key
|
||||||
|
keyGenerator.init(keyGenParameterSpec);
|
||||||
|
return keyGenerator.generateKey();
|
||||||
|
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (NoSuchProviderException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (InvalidAlgorithmParameterException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the key associated with the current alias.
|
||||||
|
*/
|
||||||
|
public void deleteStoredKey() {
|
||||||
|
try {
|
||||||
|
KeyStore ks = KeyStore.getInstance(KEYSTORE_PROVIDER);
|
||||||
|
ks.load(null);
|
||||||
|
ks.deleteEntry(KEYSTORE_ALIAS);
|
||||||
|
} catch (KeyStoreException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (CertificateException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt data with given algorithm and key associated with alias.
|
||||||
|
* @param data data to encrypt.
|
||||||
|
* @return encrypted data as String.
|
||||||
|
* @throws NoSuchPaddingException padding not found
|
||||||
|
* @throws NoSuchAlgorithmException algorithm not found
|
||||||
|
* @throws InvalidKeyException invalid key
|
||||||
|
* @throws BadPaddingException bad padding
|
||||||
|
* @throws IllegalBlockSizeException illegal block size
|
||||||
|
*/
|
||||||
|
public String encrypt(String data) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
|
||||||
|
SecretKey secretKey;
|
||||||
|
|
||||||
|
if (isInitialized()) {
|
||||||
|
secretKey = getStoredKey();
|
||||||
|
} else {
|
||||||
|
secretKey = generateKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
final Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
|
||||||
|
|
||||||
|
byte[] byteData = data.getBytes(StandardCharsets.UTF_8);
|
||||||
|
byte[] combined = getCombinedArray(cipher.getIV(), cipher.doFinal(byteData));
|
||||||
|
return Base64.encodeToString(combined, Base64.NO_WRAP);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts data with given algorithm and key associated with alias.
|
||||||
|
* @param input encrypted data.
|
||||||
|
* @return decrypted data as String.
|
||||||
|
* @throws NoSuchPaddingException padding not found
|
||||||
|
* @throws NoSuchAlgorithmException algorithm not found
|
||||||
|
* @throws InvalidAlgorithmParameterException invalid algorithm parameters
|
||||||
|
* @throws InvalidKeyException invalid key
|
||||||
|
* @throws BadPaddingException bad padding
|
||||||
|
* @throws IllegalBlockSizeException illegal block size
|
||||||
|
*/
|
||||||
|
public String decrypt(String input) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
|
||||||
|
|
||||||
|
// extract iv from save data
|
||||||
|
// String[] parts = input.split(":::");
|
||||||
|
// byte[] iv = Base64.decode(parts[0], Base64.DEFAULT);
|
||||||
|
// byte[] data = Base64.decode(parts[1], Base64.DEFAULT);
|
||||||
|
|
||||||
|
byte[] in = Base64.decode(input, Base64.NO_WRAP);
|
||||||
|
IvAndData ivAndData = splitCombinedArray(in, 12);
|
||||||
|
|
||||||
|
final Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
|
||||||
|
final GCMParameterSpec gcmSpec = new GCMParameterSpec(128, ivAndData.iv);
|
||||||
|
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, getStoredKey(), gcmSpec);
|
||||||
|
|
||||||
|
return new String(cipher.doFinal(ivAndData.data), StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all aliases and the associated keys.
|
||||||
|
* Note: all encrypted data cannot be decrypted anymore!
|
||||||
|
*/
|
||||||
|
public static void deleteAllStoredKeys() {
|
||||||
|
try {
|
||||||
|
|
||||||
|
KeyStore ks = KeyStore.getInstance(KEYSTORE_PROVIDER);
|
||||||
|
ks.load(null);
|
||||||
|
|
||||||
|
Log.i(TAG, "Size: " + ks.size());
|
||||||
|
|
||||||
|
Enumeration<String> aliases = ks.aliases();
|
||||||
|
|
||||||
|
while (aliases.hasMoreElements()) {
|
||||||
|
String alias = aliases.nextElement();
|
||||||
|
ks.deleteEntry(alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (KeyStoreException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (CertificateException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combine IV and encrypted data.
|
||||||
|
* @param iv initialisation vector
|
||||||
|
* @param encryptedData encrypted data
|
||||||
|
* @return combination of iv and encrypted data
|
||||||
|
*/
|
||||||
|
private byte[] getCombinedArray(byte[] iv, byte[] encryptedData) {
|
||||||
|
|
||||||
|
Log.i(TAG, "iv length = " + iv.length);
|
||||||
|
|
||||||
|
byte[] combined = new byte[iv.length + encryptedData.length];
|
||||||
|
for (int i = 0; i < combined.length; ++i) {
|
||||||
|
combined[i] = i < iv.length ? iv[i] : encryptedData[i - iv.length];
|
||||||
|
}
|
||||||
|
return combined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IvAndData splitCombinedArray(byte[] input, int ivLength) {
|
||||||
|
byte[] iv = Arrays.copyOfRange(input, 0, ivLength);
|
||||||
|
byte[] data = Arrays.copyOfRange(input, ivLength, input.length);
|
||||||
|
return new IvAndData(iv, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IvAndData {
|
||||||
|
IvAndData(byte[] iv, byte[] data) {
|
||||||
|
this.iv = iv;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] iv;
|
||||||
|
byte[] data;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<alpha
|
||||||
|
android:interpolator="@android:anim/accelerate_interpolator"
|
||||||
|
android:fromAlpha="0.0"
|
||||||
|
android:toAlpha="1.0"
|
||||||
|
android:duration="@android:integer/config_shortAnimTime" />
|
||||||
|
</set>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<alpha
|
||||||
|
android:interpolator="@android:anim/accelerate_interpolator"
|
||||||
|
android:fromAlpha="1.0"
|
||||||
|
android:toAlpha="0.0"
|
||||||
|
android:duration="@android:integer/config_shortAnimTime" />
|
||||||
|
</set>
|
|
@ -1,16 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
|
|
||||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:shareInterpolator="true"
|
|
||||||
android:interpolator="@android:anim/accelerate_interpolator">
|
|
||||||
|
|
||||||
<translate
|
|
||||||
android:duration="150"
|
|
||||||
android:fromYDelta="0%"
|
|
||||||
android:toYDelta="15%"/>
|
|
||||||
|
|
||||||
<alpha
|
|
||||||
android:duration="150"
|
|
||||||
android:fromAlpha="1"
|
|
||||||
android:toAlpha="0"/>
|
|
||||||
</set>
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<translate android:fromXDelta="50%p" android:toXDelta="0"
|
||||||
|
android:duration="@android:integer/config_mediumAnimTime"/>
|
||||||
|
<alpha android:fromAlpha="0.0" android:toAlpha="1.0"
|
||||||
|
android:duration="@android:integer/config_mediumAnimTime" />
|
||||||
|
</set>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<translate android:fromXDelta="0" android:toXDelta="-50%p"
|
||||||
|
android:duration="@android:integer/config_mediumAnimTime"/>
|
||||||
|
<alpha android:fromAlpha="1.0" android:toAlpha="0.0"
|
||||||
|
android:duration="@android:integer/config_mediumAnimTime" />
|
||||||
|
</set>
|
|
@ -1,21 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
|
|
||||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:shareInterpolator="false">
|
|
||||||
|
|
||||||
<translate
|
|
||||||
android:interpolator="@android:anim/decelerate_interpolator"
|
|
||||||
android:duration="200"
|
|
||||||
android:fromYDelta="15%"
|
|
||||||
android:toYDelta="0%"/>
|
|
||||||
|
|
||||||
<scale
|
|
||||||
android:interpolator="@android:anim/decelerate_interpolator"
|
|
||||||
android:duration="120"
|
|
||||||
android:fromXScale="0.8"
|
|
||||||
android:fromYScale="0.6"
|
|
||||||
android:pivotX="50%"
|
|
||||||
android:pivotY="100%"
|
|
||||||
android:toXScale="1.0"
|
|
||||||
android:toYScale="1.0"/>
|
|
||||||
</set>
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<objectAnimator
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:duration="500"
|
||||||
|
android:repeatCount="infinite"
|
||||||
|
android:repeatMode="restart"
|
||||||
|
android:propertyName="rotation"
|
||||||
|
android:valueFrom="0"
|
||||||
|
android:valueTo="-180"
|
||||||
|
android:valueType="floatType"/>
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
|
||||||
|
<objectAnimator
|
||||||
|
android:interpolator="@android:interpolator/accelerate_cubic"
|
||||||
|
android:duration="300"
|
||||||
|
android:propertyName="x"
|
||||||
|
android:valueFrom="1000"
|
||||||
|
android:valueTo="0"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
|
||||||
|
</set>
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
|
||||||
|
<objectAnimator
|
||||||
|
android:interpolator="@android:interpolator/accelerate_cubic"
|
||||||
|
android:duration="300"
|
||||||
|
android:propertyName="x"
|
||||||
|
android:valueFrom="-1000"
|
||||||
|
android:valueTo="0"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
|
||||||
|
</set>
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="300"
|
||||||
|
android:propertyName="x"
|
||||||
|
android:valueFrom="0"
|
||||||
|
android:valueTo="1000"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
|
||||||
|
</set>
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="300"
|
||||||
|
android:propertyName="x"
|
||||||
|
android:valueFrom="0"
|
||||||
|
android:valueTo="-1000"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
|
||||||
|
</set>
|
|
@ -1,34 +1,34 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:aapt="http://schemas.android.com/aapt"
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
android:width="108dp"
|
android:width="108dp"
|
||||||
android:height="108dp"
|
android:height="108dp"
|
||||||
android:viewportHeight="108"
|
android:viewportHeight="108"
|
||||||
android:viewportWidth="108">
|
android:viewportWidth="108">
|
||||||
<path
|
<path
|
||||||
android:fillType="evenOdd"
|
android:fillType="evenOdd"
|
||||||
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
||||||
android:strokeColor="#00000000"
|
android:strokeColor="#00000000"
|
||||||
android:strokeWidth="1">
|
android:strokeWidth="1">
|
||||||
<aapt:attr name="android:fillColor">
|
<aapt:attr name="android:fillColor">
|
||||||
<gradient
|
<gradient
|
||||||
android:endX="78.5885"
|
android:endX="78.5885"
|
||||||
android:endY="90.9159"
|
android:endY="90.9159"
|
||||||
android:startX="48.7653"
|
android:startX="48.7653"
|
||||||
android:startY="61.0927"
|
android:startY="61.0927"
|
||||||
android:type="linear">
|
android:type="linear">
|
||||||
<item
|
<item
|
||||||
android:color="#44000000"
|
android:color="#44000000"
|
||||||
android:offset="0.0"/>
|
android:offset="0.0" />
|
||||||
<item
|
<item
|
||||||
android:color="#00000000"
|
android:color="#00000000"
|
||||||
android:offset="1.0"/>
|
android:offset="1.0" />
|
||||||
</gradient>
|
</gradient>
|
||||||
</aapt:attr>
|
</aapt:attr>
|
||||||
</path>
|
</path>
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FFFFFF"
|
android:fillColor="#FFFFFF"
|
||||||
android:fillType="nonZero"
|
android:fillType="nonZero"
|
||||||
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
||||||
android:strokeColor="#00000000"
|
android:strokeColor="#00000000"
|
||||||
android:strokeWidth="1"/>
|
android:strokeWidth="1" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<animated-vector
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:drawable="@drawable/ic_sync_black_24dp">
|
||||||
|
<target
|
||||||
|
android:name="rotation"
|
||||||
|
android:animation="@animator/rotation_cw"/>
|
||||||
|
</animated-vector>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_checked="true" android:color="@color/white" />
|
||||||
|
<item android:color="@color/white_50"/>
|
||||||
|
</selector>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<vector android:height="24dp" android:tint="#B4B4B4"
|
||||||
|
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FF000000" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,5c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM12,19.2c-2.5,0 -4.71,-1.28 -6,-3.22 0.03,-1.99 4,-3.08 6,-3.08 1.99,0 5.97,1.09 6,3.08 -1.29,1.94 -3.5,3.22 -6,3.22z"/>
|
||||||
|
</vector>
|
|
@ -1,5 +1,5 @@
|
||||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<path android:fillColor="#FF000000" android:pathData="M12,4l-1.41,1.41L16.17,11H4v2h12.17l-5.58,5.59L12,20l8,-8z"/>
|
<path android:fillColor="#FF000000" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
||||||
</vector>
|
</vector>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFF"
|
||||||
|
android:pathData="M19,11H7.83l4.88,-4.88c0.39,-0.39 0.39,-1.03 0,-1.42 -0.39,-0.39 -1.02,-0.39 -1.41,0l-6.59,6.59c-0.39,0.39 -0.39,1.02 0,1.41l6.59,6.59c0.39,0.39 1.02,0.39 1.41,0 0.39,-0.39 0.39,-1.02 0,-1.41L7.83,13H19c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFF"
|
||||||
|
android:pathData="M5,13h11.17l-4.88,4.88c-0.39,0.39 -0.39,1.03 0,1.42 0.39,0.39 1.02,0.39 1.41,0l6.59,-6.59c0.39,-0.39 0.39,-1.02 0,-1.41l-6.58,-6.6c-0.39,-0.39 -1.02,-0.39 -1.41,0 -0.39,0.39 -0.39,1.02 0,1.41L16.17,11H5c-0.55,0 -1,0.45 -1,1s0.45,1 1,1z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<vector android:height="24dp" android:tint="#B4B4B4"
|
||||||
|
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FF000000" android:pathData="M12,6v3l4,-4 -4,-4v3c-4.42,0 -8,3.58 -8,8 0,1.57 0.46,3.03 1.24,4.26L6.7,14.8c-0.45,-0.83 -0.7,-1.79 -0.7,-2.8 0,-3.31 2.69,-6 6,-6zM18.76,7.74L17.3,9.2c0.44,0.84 0.7,1.79 0.7,2.8 0,3.31 -2.69,6 -6,6v-3l-4,4 4,4v-3c4.42,0 8,-3.58 8,-8 0,-1.57 -0.46,-3.03 -1.24,-4.26z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,13 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="100dp"
|
||||||
|
android:height="20dp"
|
||||||
|
android:viewportWidth="100"
|
||||||
|
android:viewportHeight="20">
|
||||||
|
<path
|
||||||
|
android:pathData="M21.184,20C21.541,19.87 21.904,19.736 22.272,19.598C22.635,19.463 23.871,19 24.04,18.937C33.64,15.348 39.647,14 50,14C60.271,14 65.362,15.222 74.629,18.928C75.584,19.311 76.498,19.668 77.379,20L83.604,20C81.093,19.269 78.465,18.309 75.371,17.072C65.888,13.278 60.562,12 50,12C39.374,12 33.145,13.397 23.34,17.063C23.169,17.127 21.934,17.59 21.573,17.725C19.098,18.648 16.913,19.399 14.849,20L21.184,20L21.184,20ZM21.184,0C13.258,2.892 8.077,4 0,4L0,4L0,2C5.744,2 9.951,1.426 14.849,0L21.184,0L21.184,0ZM77.379,0C85.239,2.966 90.502,4 100,4L100,2C93.158,2 88.614,1.458 83.604,0L77.379,0L77.379,0ZM0,14C8.441,14 13.718,12.79 22.272,9.598C22.635,9.463 23.871,9 24.04,8.937C33.64,5.348 39.647,4 50,4C60.271,4 65.362,5.222 74.629,8.928C84.112,12.722 89.438,14 100,14L100,12C89.729,12 84.638,10.778 75.371,7.072C65.888,3.278 60.562,2 50,2C39.374,2 33.145,3.397 23.34,7.063C23.169,7.127 21.934,7.59 21.573,7.725C13.224,10.84 8.164,12 0,12L0,14L0,14L0,14Z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:fillColor="@color/colorAccent"
|
||||||
|
android:fillAlpha="0.5"
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:strokeColor="#00000000"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFF"
|
||||||
|
android:pathData="M9,16.17L5.53,12.7c-0.39,-0.39 -1.02,-0.39 -1.41,0 -0.39,0.39 -0.39,1.02 0,1.41l4.18,4.18c0.39,0.39 1.02,0.39 1.41,0L20.29,7.71c0.39,-0.39 0.39,-1.02 0,-1.41 -0.39,-0.39 -1.02,-0.39 -1.41,0L9,16.17z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFF"
|
||||||
|
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM15.88,8.29L10,14.17l-1.88,-1.88c-0.39,-0.39 -1.02,-0.39 -1.41,0 -0.39,0.39 -0.39,1.02 0,1.41l2.59,2.59c0.39,0.39 1.02,0.39 1.41,0L17.3,9.7c0.39,-0.39 0.39,-1.02 0,-1.41 -0.39,-0.39 -1.03,-0.39 -1.42,0z"/>
|
||||||
|
</vector>
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFF"
|
||||||
|
android:pathData="M18.3,5.71c-0.39,-0.39 -1.02,-0.39 -1.41,0L12,10.59 7.11,5.7c-0.39,-0.39 -1.02,-0.39 -1.41,0 -0.39,0.39 -0.39,1.02 0,1.41L10.59,12 5.7,16.89c-0.39,0.39 -0.39,1.02 0,1.41 0.39,0.39 1.02,0.39 1.41,0L12,13.41l4.89,4.89c0.39,0.39 1.02,0.39 1.41,0 0.39,-0.39 0.39,-1.02 0,-1.41L13.41,12l4.89,-4.89c0.38,-0.38 0.38,-1.02 0,-1.4z"/>
|
||||||
|
</vector>
|
|
@ -1,5 +1,5 @@
|
||||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<path android:fillColor="#FF000000" android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96z"/>
|
<path android:fillColor="#FF000000" android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM17,13l-5,5 -5,-5h3V9h4v4h3z"/>
|
||||||
</vector>
|
</vector>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
|
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FF000000" android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM14,13v4h-4v-4H7l5,-5 5,5h-3z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
|
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FF000000" android:pathData="M9.4,16.6L4.8,12l4.6,-4.6L8,6l-6,6 6,6 1.4,-1.4zM14.6,16.6l4.6,-4.6 -4.6,-4.6L16,6l6,6 -6,6 -1.4,-1.4z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2L18,9c0,-1.1 -0.9,-2 -2,-2L8,7c-1.1,0 -2,0.9 -2,2v10zM9.17,11.17c0.39,-0.39 1.02,-0.39 1.41,0L12,12.59l1.42,-1.42c0.39,-0.39 1.02,-0.39 1.41,0 0.39,0.39 0.39,1.02 0,1.41L13.41,14l1.42,1.42c0.39,0.39 0.39,1.02 0,1.41 -0.39,0.39 -1.02,0.39 -1.41,0L12,15.41l-1.42,1.42c-0.39,0.39 -1.02,0.39 -1.41,0 -0.39,-0.39 -0.39,-1.02 0,-1.41L10.59,14l-1.42,-1.42c-0.39,-0.38 -0.39,-1.02 0,-1.41zM15.5,4l-0.71,-0.71c-0.18,-0.18 -0.44,-0.29 -0.7,-0.29L9.91,3c-0.26,0 -0.52,0.11 -0.7,0.29L8.5,4L6,4c-0.55,0 -1,0.45 -1,1s0.45,1 1,1h12c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1h-2.5z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
|
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FF000000" android:pathData="M14,2L6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6zM16,18L8,18v-2h8v2zM16,14L8,14v-2h8v2zM13,9L13,3.5L18.5,9L13,9z"/>
|
||||||
|
</vector>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue