diff --git a/.gitignore b/.gitignore index aedec80..19b0f88 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ +# Source: https://github.com/github/gitignore/blob/master/Android.gitignore + # Built application files *.apk *.ap_ +*.aab # Files for the ART/Dalvik VM *.dex @@ -12,6 +15,7 @@ bin/ gen/ out/ +release/ # Gradle files .gradle/ @@ -40,17 +44,22 @@ captures/ .idea/assetWizardSettings.xml .idea/dictionaries .idea/libraries +# Android Studio 3 in .gitignore file. .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 -# 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 +#*.keystore # External native build folder generated in Android Studio 2.2 and later .externalNativeBuild # Google Services (e.g. APIs or Firebase) -google-services.json +# google-services.json # Freeline freeline.py @@ -64,7 +73,12 @@ fastlane/screenshots fastlane/test_output fastlane/readme.md -# custom -/gradlew -/gradlew.bat -/local.properties \ No newline at end of file +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index a55e7a1..0000000 --- a/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..15a15b2 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 37a7509..da7759f 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,44 @@ - + + + + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 319e325..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml deleted file mode 100644 index e96534f..0000000 --- a/.idea/uiDesigner.xml +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index 53d2489..b1da1cd 100644 --- a/README.md +++ b/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 ![Alt text](./poster/mispbump.png) diff --git a/app/build.gradle b/app/build.gradle index b623d6c..43f699e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,44 +1,53 @@ apply plugin: 'com.android.application' +apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-android' android { - compileSdkVersion 27 + compileSdkVersion 28 defaultConfig { - applicationId "de.overview.wg.its.mispbump" - minSdkVersion 21 - targetSdkVersion 27 + applicationId "lu.circl.mispbump" + minSdkVersion 23 + targetSdkVersion 28 versionCode 1 versionName "1.0" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables.useSupportLibrary = true } buildTypes { release { minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } 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' - implementation 'com.android.support:design:27.1.1' - implementation 'com.android.support:gridlayout-v7:27.1.1' - implementation 'com.android.support:recyclerview-v7:27.1.1' - 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' + // retrofit + implementation 'com.squareup.retrofit2:retrofit:2.6.0' + implementation 'com.squareup.retrofit2:converter-gson:2.5.0' + implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0' - 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' - implementation 'com.github.kenglxn.QRGen:android:2.5.0' - implementation 'org.mongodb:bson:3.8.0' - implementation 'com.google.code.gson:gson:2.8.5' + // barcode generation + implementation 'com.journeyapps:zxing-android-embedded:3.2.0@aar' + implementation 'com.google.zxing:core:3.4.0' - implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation 'com.android.support:support-v4:27.1.1' + implementation fileTree(dir: 'libs', include: ['*.jar']) testImplementation 'junit:junit:4.12' - androidTestImplementation 'com.android.support.test:runner:1.0.2' - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} +repositories { + mavenCentral() } diff --git a/app/src/androidTest/java/de/overview/wg/its/mispbump/ExampleInstrumentedTest.java b/app/src/androidTest/java/lu/circl/mispbump/ExampleInstrumentedTest.java similarity index 69% rename from app/src/androidTest/java/de/overview/wg/its/mispbump/ExampleInstrumentedTest.java rename to app/src/androidTest/java/lu/circl/mispbump/ExampleInstrumentedTest.java index 7cb3cc1..2eda88f 100644 --- a/app/src/androidTest/java/de/overview/wg/its/mispbump/ExampleInstrumentedTest.java +++ b/app/src/androidTest/java/lu/circl/mispbump/ExampleInstrumentedTest.java @@ -1,8 +1,8 @@ -package de.overview.wg.its.mispbump; +package lu.circl.mispbump; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; @@ -21,6 +21,6 @@ public class ExampleInstrumentedTest { // Context of the app under test. Context appContext = InstrumentationRegistry.getTargetContext(); - assertEquals("de.overview.wg.its.mispbump", appContext.getPackageName()); + assertEquals("lu.circl.mispbump", appContext.getPackageName()); } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e03baac..b0c9e79 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,44 +1,47 @@ + xmlns:tools="http://schemas.android.com/tools" + package="lu.circl.mispbump"> - - + + - - - - + + + + + + - - - - - - - - - - - - + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/de/overview/wg/its/mispbump/MainActivity.java b/app/src/main/java/de/overview/wg/its/mispbump/MainActivity.java deleted file mode 100644 index 7d8681d..0000000 --- a/app/src/main/java/de/overview/wg/its/mispbump/MainActivity.java +++ /dev/null @@ -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 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)); - } -} diff --git a/app/src/main/java/de/overview/wg/its/mispbump/MyOrganisationActivity.java b/app/src/main/java/de/overview/wg/its/mispbump/MyOrganisationActivity.java deleted file mode 100644 index 7298823..0000000 --- a/app/src/main/java/de/overview/wg/its/mispbump/MyOrganisationActivity.java +++ /dev/null @@ -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 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(); - } -} diff --git a/app/src/main/java/de/overview/wg/its/mispbump/QrSyncActivity.java b/app/src/main/java/de/overview/wg/its/mispbump/QrSyncActivity.java deleted file mode 100644 index 7d073ba..0000000 --- a/app/src/main/java/de/overview/wg/its/mispbump/QrSyncActivity.java +++ /dev/null @@ -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(); - - } - }); - - } -} diff --git a/app/src/main/java/de/overview/wg/its/mispbump/SyncUploadActivity.java b/app/src/main/java/de/overview/wg/its/mispbump/SyncUploadActivity.java deleted file mode 100644 index 358801f..0000000 --- a/app/src/main/java/de/overview/wg/its/mispbump/SyncUploadActivity.java +++ /dev/null @@ -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 syncedPartnerList = preferenceManager.getSyncedPartnerList(); - - SyncedPartner sp = new SyncedPartner(partnerOrganisation.getName(), partnerServer.getUrl()); - sp.generateTimeStamp(); - - syncedPartnerList.add(sp); - preferenceManager.setSyncedPartnerList(syncedPartnerList); - } -} diff --git a/app/src/main/java/de/overview/wg/its/mispbump/adapter/OrganisationInfoEntryAdapter.java b/app/src/main/java/de/overview/wg/its/mispbump/adapter/OrganisationInfoEntryAdapter.java deleted file mode 100644 index 58c81c5..0000000 --- a/app/src/main/java/de/overview/wg/its/mispbump/adapter/OrganisationInfoEntryAdapter.java +++ /dev/null @@ -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 { - - private Context context; - private List 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 list) { - this.list = list; - notifyDataSetChanged(); - } -} diff --git a/app/src/main/java/de/overview/wg/its/mispbump/adapter/SyncedPartnerAdapter.java b/app/src/main/java/de/overview/wg/its/mispbump/adapter/SyncedPartnerAdapter.java deleted file mode 100644 index 47661cf..0000000 --- a/app/src/main/java/de/overview/wg/its/mispbump/adapter/SyncedPartnerAdapter.java +++ /dev/null @@ -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 { - - private List 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 syncedPartnerList) { - this.syncedPartnerList = syncedPartnerList; - this.context = context; - } - - public void setSyncedPartnerList(List 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(); - } -} diff --git a/app/src/main/java/de/overview/wg/its/mispbump/adapter/UploadStateAdapter.java b/app/src/main/java/de/overview/wg/its/mispbump/adapter/UploadStateAdapter.java deleted file mode 100644 index 9400d77..0000000 --- a/app/src/main/java/de/overview/wg/its/mispbump/adapter/UploadStateAdapter.java +++ /dev/null @@ -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 { - - 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; - } - -} diff --git a/app/src/main/java/de/overview/wg/its/mispbump/auxiliary/AESSecurity.java b/app/src/main/java/de/overview/wg/its/mispbump/auxiliary/AESSecurity.java deleted file mode 100644 index a50d8db..0000000 --- a/app/src/main/java/de/overview/wg/its/mispbump/auxiliary/AESSecurity.java +++ /dev/null @@ -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; - } -} diff --git a/app/src/main/java/de/overview/wg/its/mispbump/auxiliary/PreferenceManager.java b/app/src/main/java/de/overview/wg/its/mispbump/auxiliary/PreferenceManager.java deleted file mode 100644 index 009f838..0000000 --- a/app/src/main/java/de/overview/wg/its/mispbump/auxiliary/PreferenceManager.java +++ /dev/null @@ -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 getSyncedPartnerList() { - String list = syncedInstancesPreferences.getString(PREF_KEY_SYNCED_ORGANISATIONS, ""); - Type type = new TypeToken>() {}.getType(); - return new Gson().fromJson(list, type); - } - public void setSyncedPartnerList(List 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; - } -} - diff --git a/app/src/main/java/de/overview/wg/its/mispbump/auxiliary/RandomString.java b/app/src/main/java/de/overview/wg/its/mispbump/auxiliary/RandomString.java deleted file mode 100644 index 0cac2c8..0000000 --- a/app/src/main/java/de/overview/wg/its/mispbump/auxiliary/RandomString.java +++ /dev/null @@ -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); - } - -} \ No newline at end of file diff --git a/app/src/main/java/de/overview/wg/its/mispbump/auxiliary/ReadableError.java b/app/src/main/java/de/overview/wg/its/mispbump/auxiliary/ReadableError.java deleted file mode 100644 index 722bde3..0000000 --- a/app/src/main/java/de/overview/wg/its/mispbump/auxiliary/ReadableError.java +++ /dev/null @@ -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(); - } - -} diff --git a/app/src/main/java/de/overview/wg/its/mispbump/auxiliary/TempAuth.java b/app/src/main/java/de/overview/wg/its/mispbump/auxiliary/TempAuth.java deleted file mode 100644 index db88858..0000000 --- a/app/src/main/java/de/overview/wg/its/mispbump/auxiliary/TempAuth.java +++ /dev/null @@ -1,7 +0,0 @@ -package de.overview.wg.its.mispbump.auxiliary; - -public class TempAuth { - - public static String TMP_AUTH_KEY; - -} diff --git a/app/src/main/java/de/overview/wg/its/mispbump/cam/AutoFitTextureView.java b/app/src/main/java/de/overview/wg/its/mispbump/cam/AutoFitTextureView.java deleted file mode 100644 index 75960cd..0000000 --- a/app/src/main/java/de/overview/wg/its/mispbump/cam/AutoFitTextureView.java +++ /dev/null @@ -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); - } - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/de/overview/wg/its/mispbump/model/Organisation.java b/app/src/main/java/de/overview/wg/its/mispbump/model/Organisation.java deleted file mode 100644 index 2b68655..0000000 --- a/app/src/main/java/de/overview/wg/its/mispbump/model/Organisation.java +++ /dev/null @@ -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; - } -} diff --git a/app/src/main/java/de/overview/wg/its/mispbump/model/PublicKeyQr.java b/app/src/main/java/de/overview/wg/its/mispbump/model/PublicKeyQr.java deleted file mode 100644 index 50a1602..0000000 --- a/app/src/main/java/de/overview/wg/its/mispbump/model/PublicKeyQr.java +++ /dev/null @@ -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; - } -} diff --git a/app/src/main/java/de/overview/wg/its/mispbump/model/Server.java b/app/src/main/java/de/overview/wg/its/mispbump/model/Server.java deleted file mode 100644 index dd03b8a..0000000 --- a/app/src/main/java/de/overview/wg/its/mispbump/model/Server.java +++ /dev/null @@ -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; - } - -} diff --git a/app/src/main/java/de/overview/wg/its/mispbump/model/StringPair.java b/app/src/main/java/de/overview/wg/its/mispbump/model/StringPair.java deleted file mode 100644 index 5e24034..0000000 --- a/app/src/main/java/de/overview/wg/its/mispbump/model/StringPair.java +++ /dev/null @@ -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; - } -} diff --git a/app/src/main/java/de/overview/wg/its/mispbump/model/SyncInformationQr.java b/app/src/main/java/de/overview/wg/its/mispbump/model/SyncInformationQr.java deleted file mode 100644 index 6d19675..0000000 --- a/app/src/main/java/de/overview/wg/its/mispbump/model/SyncInformationQr.java +++ /dev/null @@ -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; - } -} diff --git a/app/src/main/java/de/overview/wg/its/mispbump/model/SyncedPartner.java b/app/src/main/java/de/overview/wg/its/mispbump/model/SyncedPartner.java deleted file mode 100644 index 261b6fa..0000000 --- a/app/src/main/java/de/overview/wg/its/mispbump/model/SyncedPartner.java +++ /dev/null @@ -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; - } -} diff --git a/app/src/main/java/de/overview/wg/its/mispbump/model/UploadState.java b/app/src/main/java/de/overview/wg/its/mispbump/model/UploadState.java deleted file mode 100644 index 3538e27..0000000 --- a/app/src/main/java/de/overview/wg/its/mispbump/model/UploadState.java +++ /dev/null @@ -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; - } - -} diff --git a/app/src/main/java/de/overview/wg/its/mispbump/model/User.java b/app/src/main/java/de/overview/wg/its/mispbump/model/User.java deleted file mode 100644 index 44b23fe..0000000 --- a/app/src/main/java/de/overview/wg/its/mispbump/model/User.java +++ /dev/null @@ -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; - } -} diff --git a/app/src/main/java/de/overview/wg/its/mispbump/network/JsonArrayRequestWithJsonObject.java b/app/src/main/java/de/overview/wg/its/mispbump/network/JsonArrayRequestWithJsonObject.java deleted file mode 100644 index 7cbaff1..0000000 --- a/app/src/main/java/de/overview/wg/its/mispbump/network/JsonArrayRequestWithJsonObject.java +++ /dev/null @@ -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 { - /** - * 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 listener, Response.ErrorListener errorListener) { - super(method, url, (jsonRequest == null) ? null : jsonRequest.toString(), listener, errorListener); - } - - @Override - protected Response 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)); - - } - } -} diff --git a/app/src/main/java/de/overview/wg/its/mispbump/network/MispRequest.java b/app/src/main/java/de/overview/wg/its/mispbump/network/MispRequest.java deleted file mode 100644 index 5c06071..0000000 --- a/app/src/main/java/de/overview/wg/its/mispbump/network/MispRequest.java +++ /dev/null @@ -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 listener = new Response.Listener() { - @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 listener = new Response.Listener() { - @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 listener = new Response.Listener() { - @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 listener = new Response.Listener() { - @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 listener = new Response.Listener() { - @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 listener = new Response.Listener() { - @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 listener = new Response.Listener() { - @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 listener = new Response.Listener() { - @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 listener = new Response.Listener() { - @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 listener, - Response.ErrorListener errorListener) { - - return new JsonArrayRequestWithJsonObject(method, url, body, listener, errorListener) { - @Override - public Map getHeaders() { - Map 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 listener, - Response.ErrorListener errorListener) { - - return new JsonObjectRequest(method, url, body, listener, errorListener) { - @Override - public Map getHeaders() { - Map 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); - } -} diff --git a/app/src/main/java/lu/circl/mispbump/activities/HomeActivity.java b/app/src/main/java/lu/circl/mispbump/activities/HomeActivity.java new file mode 100644 index 0000000..b35b766 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/activities/HomeActivity.java @@ -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 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() { + @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() { + @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); + } +} diff --git a/app/src/main/java/lu/circl/mispbump/activities/LoginActivity.java b/app/src/main/java/lu/circl/mispbump/activities/LoginActivity.java new file mode 100644 index 0000000..f2ddce9 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/activities/LoginActivity.java @@ -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); + } +} diff --git a/app/src/main/java/lu/circl/mispbump/activities/PreferenceActivity.java b/app/src/main/java/lu/circl/mispbump/activities/PreferenceActivity.java new file mode 100644 index 0000000..9785f9e --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/activities/PreferenceActivity.java @@ -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(); + } + }); + } +} diff --git a/app/src/main/java/lu/circl/mispbump/activities/ProfileActivity.java b/app/src/main/java/lu/circl/mispbump/activities/ProfileActivity.java new file mode 100644 index 0000000..8279469 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/activities/ProfileActivity.java @@ -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(); + } +} diff --git a/app/src/main/java/lu/circl/mispbump/activities/StartUpActivity.java b/app/src/main/java/lu/circl/mispbump/activities/StartUpActivity.java new file mode 100644 index 0000000..e0f5b18 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/activities/StartUpActivity.java @@ -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; + } +} diff --git a/app/src/main/java/lu/circl/mispbump/activities/SyncActivity.java b/app/src/main/java/lu/circl/mispbump/activities/SyncActivity.java new file mode 100644 index 0000000..2d91574 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/activities/SyncActivity.java @@ -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; + } + } + +} diff --git a/app/src/main/java/lu/circl/mispbump/activities/UploadActivity.java b/app/src/main/java/lu/circl/mispbump/activities/UploadActivity.java new file mode 100644 index 0000000..01cae03 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/activities/UploadActivity.java @@ -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 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; + } +} diff --git a/app/src/main/java/lu/circl/mispbump/adapters/SyncAdapter.java b/app/src/main/java/lu/circl/mispbump/adapters/SyncAdapter.java new file mode 100644 index 0000000..973d443 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/adapters/SyncAdapter.java @@ -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 { + + private Context context; + private List uploadInformationList; + private IOnItemClickListener 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 listener) { + delete.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + listener.onItemClick(item); + } + }); + } + + void bindRetryListener(final UploadInformation item, final IOnItemClickListener 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 uploadInformationList) { + this.uploadInformationList = uploadInformationList; + notifyDataSetChanged(); + } + + public void setOnDeleteClickListener(IOnItemClickListener listener) { + deleteListener = listener; + } + + public void setOnRetryClickListener(IOnItemClickListener 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(); + } + +} diff --git a/app/src/main/java/lu/circl/mispbump/adapters/UserAdapter.java b/app/src/main/java/lu/circl/mispbump/adapters/UserAdapter.java new file mode 100644 index 0000000..e3f7c00 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/adapters/UserAdapter.java @@ -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 { + + private List> 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(); + } + +} diff --git a/app/src/main/java/lu/circl/mispbump/auxiliary/DialogManager.java b/app/src/main/java/lu/circl/mispbump/auxiliary/DialogManager.java new file mode 100644 index 0000000..54597d0 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/auxiliary/DialogManager.java @@ -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(); + } +} diff --git a/app/src/main/java/lu/circl/mispbump/auxiliary/KeyValue.java b/app/src/main/java/lu/circl/mispbump/auxiliary/KeyValue.java new file mode 100644 index 0000000..b0e02e1 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/auxiliary/KeyValue.java @@ -0,0 +1,16 @@ +package lu.circl.mispbump.auxiliary; + +public class KeyValue { + 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; + } +} diff --git a/app/src/main/java/lu/circl/mispbump/auxiliary/PreferenceManager.java b/app/src/main/java/lu/circl/mispbump/auxiliary/PreferenceManager.java new file mode 100644 index 0000000..f6b2759 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/auxiliary/PreferenceManager.java @@ -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 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 getUploadInformation() { + KeyStoreWrapper ksw = new KeyStoreWrapper(KeyStoreWrapper.UPLOAD_INFORMATION_ALIAS); + String storedUploadInfoString = preferences.getString(UPLOAD_INFO, null); + + Type type = new TypeToken>() {}.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 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 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 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(); + } +} \ No newline at end of file diff --git a/app/src/main/java/lu/circl/mispbump/auxiliary/QrCodeGenerator.java b/app/src/main/java/lu/circl/mispbump/auxiliary/QrCodeGenerator.java new file mode 100644 index 0000000..b8a58c6 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/auxiliary/QrCodeGenerator.java @@ -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 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; + } +} diff --git a/app/src/main/java/lu/circl/mispbump/auxiliary/RandomString.java b/app/src/main/java/lu/circl/mispbump/auxiliary/RandomString.java new file mode 100644 index 0000000..a0dc3d9 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/auxiliary/RandomString.java @@ -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); + } +} diff --git a/app/src/main/java/lu/circl/mispbump/auxiliary/TileDrawable.kt b/app/src/main/java/lu/circl/mispbump/auxiliary/TileDrawable.kt new file mode 100644 index 0000000..c51c535 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/auxiliary/TileDrawable.kt @@ -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 + } + +} \ No newline at end of file diff --git a/app/src/main/java/lu/circl/mispbump/cam/AutoFitTextureView.java b/app/src/main/java/lu/circl/mispbump/cam/AutoFitTextureView.java new file mode 100644 index 0000000..1574f8f --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/cam/AutoFitTextureView.java @@ -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); + } + } + } + +} diff --git a/app/src/main/java/de/overview/wg/its/mispbump/cam/CameraFragment.java b/app/src/main/java/lu/circl/mispbump/cam/CameraFragment.java similarity index 74% rename from app/src/main/java/de/overview/wg/its/mispbump/cam/CameraFragment.java rename to app/src/main/java/lu/circl/mispbump/cam/CameraFragment.java index 5e9a40a..7cbc4f4 100644 --- a/app/src/main/java/de/overview/wg/its/mispbump/cam/CameraFragment.java +++ b/app/src/main/java/lu/circl/mispbump/cam/CameraFragment.java @@ -1,6 +1,8 @@ -package de.overview.wg.its.mispbump.cam; +package lu.circl.mispbump.cam; import android.Manifest; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; @@ -8,7 +10,12 @@ import android.content.Context; import android.content.DialogInterface; import android.content.pm.PackageManager; 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.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; @@ -21,23 +28,31 @@ import android.media.ImageReader; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; -import android.renderscript.*; -import android.support.annotation.NonNull; -import android.support.v4.app.ActivityCompat; -import android.support.v4.app.DialogFragment; -import android.support.v4.app.Fragment; -import android.support.v4.content.ContextCompat; +import android.renderscript.Allocation; +import android.renderscript.Element; +import android.renderscript.RenderScript; +import android.renderscript.ScriptIntrinsicYuvToRGB; +import android.renderscript.Type; import android.util.Log; import android.util.Size; import android.util.SparseArray; 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 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.barcode.Barcode; 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.Arrays; @@ -47,19 +62,79 @@ import java.util.List; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; +import lu.circl.mispbump.R; + 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 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 public void onAttach(Context context) { super.onAttach(context); - parentActivity = (QrSyncActivity) context; } - /** - * Conversion from screen rotation to JPEG orientation. - */ private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); private static final int REQUEST_CAMERA_PERMISSION = 1; 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); } - /** - * Tag for the {@link Log}. - */ - private static final String TAG = "MISP_LOG"; - /** * 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() { + boolean processing = false; + @Override public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) { + Log.i(TAG, "Width: " + width + "; height: " + height); openCamera(width, height); } @@ -110,9 +183,10 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest @Override public void onSurfaceTextureUpdated(SurfaceTexture texture) { } - }; + private ImageProcessingThread imageProcessingThread; + /** * ID of the current {@link CameraDevice}. */ @@ -121,12 +195,12 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest /** * An {@link AutoFitTextureView} for camera preview. */ - private AutoFitTextureView mTextureView; + private AutoFitTextureView autoFitTextureView; /** * A {@link CameraCaptureSession } for camera preview. */ - private CameraCaptureSession mCaptureSession; + private CameraCaptureSession previewCaptureSession; /** * A reference to the opened {@link CameraDevice}. @@ -182,36 +256,22 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest 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 - * still image is ready to be saved. + * still bitmap is ready to be saved. */ private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { - Image image = reader.acquireNextImage(); Bitmap bitmap = YUV2Bitmap(image); - - if (bitmap != null && readQrEnabled) { - - Frame frame = new Frame.Builder().setBitmap(bitmap).build(); - SparseArray barcodes = barcodeDetector.detect(frame); - - if (barcodes.size() > 0) { - parentActivity.onReadQrCode(barcodes.valueAt(0).rawValue); - } - } - - if (image != null) { - image.close(); - } + imageProcessingThread.addToQueue(bitmap); + 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 List bigEnough = new ArrayList<>(); + // Collect the supported resolutions that are smaller than the preview Surface List notBigEnough = new ArrayList<>(); int w = aspectRatio.getWidth(); int h = aspectRatio.getHeight(); + for (Size option : choices) { if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight && option.getHeight() == option.getWidth() * h / w) { if (option.getWidth() >= textureViewWidth && option.getHeight() >= textureViewHeight) { @@ -294,19 +356,19 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View v = inflater.inflate(R.layout.fragment_camera, container, false); + hideCamView = v.findViewById(R.id.hideCam); + hideCamView.setVisibility(View.GONE); + initRenderScript(); - setUpBarcodeDetector(); - return v; } @Override public void onViewCreated(final View view, Bundle savedInstanceState) { - mTextureView = view.findViewById(R.id.texture); + autoFitTextureView = view.findViewById(R.id.texture); } @Override @@ -317,23 +379,32 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest @Override public void onResume() { super.onResume(); - 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 - // a camera and start preview from here (otherwise, we wait until the surface is ready in - // the SurfaceTextureListener). - if (mTextureView.isAvailable()) { - openCamera(mTextureView.getWidth(), mTextureView.getHeight()); - } else { - mTextureView.setSurfaceTextureListener(mSurfaceTextureListener); - } + enablePreview(); +// startBackgroundThread(); +// +// imageProcessingThread = new ImageProcessingThread(); +// imageProcessingThread.start(); +// +// // 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 +// // a camera and start preview from here (otherwise, we wait until the surface is ready in +// // the SurfaceTextureListener). +// if (autoFitTextureView.isAvailable()) { +// openCamera(autoFitTextureView.getWidth(), autoFitTextureView.getHeight()); +// } else { +// autoFitTextureView.setSurfaceTextureListener(mSurfaceTextureListener); +// } } @Override public void onPause() { closeCamera(); stopBackgroundThread(); + + if (imageProcessingThread.isAlive()) { + imageProcessingThread.isRunning = false; + } + super.onPause(); } @@ -365,31 +436,35 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest @SuppressWarnings("SuspiciousNameCombination") private void setUpCameraOutputs(int width, int height) { Activity activity = getActivity(); + assert activity != null; CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); try { for (String cameraId : manager.getCameraIdList()) { + CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); - // We don't use a front facing camera in this sample. Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING); + if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) { continue; } StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + if (map == null) { 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()); - mImageReader = ImageReader.newInstance(largest.getWidth() / 8, largest.getHeight() / 8, ImageFormat.YUV_420_888, 2); - mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler); + stillImageReader = ImageReader.newInstance(largest.getWidth() / 8, largest.getHeight() / 8, ImageFormat.YUV_420_888, 2); + stillImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler); // Find out if we need to swap dimension to get the preview size relative to sensor coordinate. int displayRotation = activity.getWindowManager().getDefaultDisplay().getRotation(); + //noinspection ConstantConditions int mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); boolean swappedDimensions = false; @@ -433,6 +508,12 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest 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 // bus' bandwidth limitation, resulting in gorgeous previews but the storage of // 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. int orientation = getResources().getConfiguration().orientation; + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { - mTextureView.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight()); + autoFitTextureView.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight()); } else { - mTextureView.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth()); + autoFitTextureView.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth()); } mCameraId = cameraId; @@ -464,8 +546,9 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest * Opens the camera specified by {@link CameraFragment#mCameraId}. */ 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(); return; } @@ -473,7 +556,6 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest setUpCameraOutputs(width, height); configureTransform(width, height); - Activity activity = getActivity(); CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); try { @@ -497,9 +579,9 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest try { mCameraOpenCloseLock.acquire(); - if (null != mCaptureSession) { - mCaptureSession.close(); - mCaptureSession = null; + if (null != previewCaptureSession) { + previewCaptureSession.close(); + previewCaptureSession = null; } if (null != mCameraDevice) { @@ -507,9 +589,9 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest mCameraDevice = null; } - if (null != mImageReader) { - mImageReader.close(); - mImageReader = null; + if (null != stillImageReader) { + stillImageReader.close(); + stillImageReader = null; } } catch (InterruptedException e) { @@ -532,8 +614,13 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest * Stops the background thread and its {@link Handler}. */ private void stopBackgroundThread() { - mBackgroundThread.quitSafely(); + + if (mBackgroundThread == null) { + return; + } + try { + mBackgroundThread.quitSafely(); mBackgroundThread.join(); mBackgroundThread = null; mBackgroundHandler = null; @@ -547,23 +634,23 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest */ private void createCameraPreviewSession() { try { - SurfaceTexture texture = mTextureView.getSurfaceTexture(); - assert texture != null; + // from AutoFitTextureView + SurfaceTexture texture = autoFitTextureView.getSurfaceTexture(); // We configure the size of default buffer to be the size of camera preview we want. texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); // This is the output Surface we need to start preview. Surface surface = new Surface(texture); - Surface mImageSurface = mImageReader.getSurface(); + Surface mImageSurface = stillImageReader.getSurface(); // 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(mImageSurface); // 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() { @Override @@ -574,14 +661,14 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest } // When the session is ready, we start displaying the preview. - mCaptureSession = cameraCaptureSession; + previewCaptureSession = cameraCaptureSession; try { // 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. mPreviewRequest = mPreviewRequestBuilder.build(); - mCaptureSession.setRepeatingRequest(mPreviewRequest, null, mBackgroundHandler); + previewCaptureSession.setRepeatingRequest(mPreviewRequest, null, mBackgroundHandler); } catch (CameraAccessException e) { 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 - * 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 viewHeight The height of `mTextureView` + * @param viewWidth The width of `autoFitTextureView` + * @param viewHeight The height of `autoFitTextureView` */ private void configureTransform(int viewWidth, int viewHeight) { Activity activity = getActivity(); - if (null == mTextureView || null == mPreviewSize || null == activity) { + if (null == autoFitTextureView || null == mPreviewSize || null == activity) { return; } int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); + Matrix matrix = new Matrix(); RectF viewRect = new RectF(0, 0, viewWidth, viewHeight); RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth()); float centerX = viewRect.centerX(); float centerY = viewRect.centerY(); + if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) { bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY()); 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) { 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. */ @@ -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 BarcodeDetector barcodeDetector; private RenderScript renderScript; @@ -763,12 +860,58 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest } public void setReadQrEnabled(boolean enabled) { - - Log.d(TAG, "setReadQrEnabled() called with: enabled = [" + 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() { barcodeDetector = new BarcodeDetector.Builder(getActivity()) .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(); } } -} \ No newline at end of file + + public void setOnQrAvailableListener(QrScanCallback callback) { + qrResultCallback = callback; + } + + public void setCameraReadyCallback(CameraReadyCallback callback) { + this.cameraReadyCallback = callback; + } +} + diff --git a/app/src/main/java/lu/circl/mispbump/custom_views/ExtendedBottomSheetBehavior.java b/app/src/main/java/lu/circl/mispbump/custom_views/ExtendedBottomSheetBehavior.java new file mode 100644 index 0000000..700489a --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/custom_views/ExtendedBottomSheetBehavior.java @@ -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 extends BottomSheetBehavior { + + 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; + } +} diff --git a/app/src/main/java/lu/circl/mispbump/custom_views/MaterialPreferenceText.java b/app/src/main/java/lu/circl/mispbump/custom_views/MaterialPreferenceText.java new file mode 100644 index 0000000..b00de52 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/custom_views/MaterialPreferenceText.java @@ -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); + } +} diff --git a/app/src/main/java/lu/circl/mispbump/custom_views/UploadAction.java b/app/src/main/java/lu/circl/mispbump/custom_views/UploadAction.java new file mode 100644 index 0000000..221fc74 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/custom_views/UploadAction.java @@ -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; + } + +} diff --git a/app/src/main/java/lu/circl/mispbump/fragments/HomeFragment.java b/app/src/main/java/lu/circl/mispbump/fragments/HomeFragment.java new file mode 100644 index 0000000..25c15b8 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/fragments/HomeFragment.java @@ -0,0 +1,6 @@ +package lu.circl.mispbump.fragments; + +import androidx.fragment.app.Fragment; + +public class HomeFragment extends Fragment { +} diff --git a/app/src/main/java/lu/circl/mispbump/fragments/SyncOptionsFragment.java b/app/src/main/java/lu/circl/mispbump/fragments/SyncOptionsFragment.java new file mode 100644 index 0000000..9e9b7a5 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/fragments/SyncOptionsFragment.java @@ -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; + } +} \ No newline at end of file diff --git a/app/src/main/java/lu/circl/mispbump/interfaces/IOnItemClickListener.java b/app/src/main/java/lu/circl/mispbump/interfaces/IOnItemClickListener.java new file mode 100644 index 0000000..2a76fdf --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/interfaces/IOnItemClickListener.java @@ -0,0 +1,5 @@ +package lu.circl.mispbump.interfaces; + +public interface IOnItemClickListener { + void onItemClick(T clickedObject); +} diff --git a/app/src/main/java/lu/circl/mispbump/models/SyncInformation.java b/app/src/main/java/lu/circl/mispbump/models/SyncInformation.java new file mode 100644 index 0000000..a0f7161 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/models/SyncInformation.java @@ -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 + '\'' + + '}'; + } +} diff --git a/app/src/main/java/lu/circl/mispbump/models/UploadInformation.java b/app/src/main/java/lu/circl/mispbump/models/UploadInformation.java new file mode 100644 index 0000000..60aca90 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/models/UploadInformation.java @@ -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 + + '}'; + } +} diff --git a/app/src/main/java/lu/circl/mispbump/restful_client/MispOrganisation.java b/app/src/main/java/lu/circl/mispbump/restful_client/MispOrganisation.java new file mode 100644 index 0000000..5bd9cd4 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/restful_client/MispOrganisation.java @@ -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; +} diff --git a/app/src/main/java/lu/circl/mispbump/restful_client/MispRestClient.java b/app/src/main/java/lu/circl/mispbump/restful_client/MispRestClient.java new file mode 100644 index 0000000..2dc850b --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/restful_client/MispRestClient.java @@ -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 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! + *

+ * 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 call = mispRestService.pyMispVersion(); + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (!response.isSuccessful()) { + if (response.code() == 403) { + callback.available(); + return; + } + + callback.unavailable(extractError(response)); + } else { + callback.available(); + } + } + + @Override + public void onFailure(Call 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 call = mispRestService.getMyUserInformation(); + + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response 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 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 call = mispRestService.getUser(userId); + + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response 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 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 call = mispRestService.addUser(user); + + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (!response.isSuccessful()) { + callback.failure(extractError(response)); + } else { + assert response.body() != null; + callback.success(response.body().user); + } + } + + @Override + public void onFailure(Call 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 call = mispRestService.getOrganisation(orgId); + + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response 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 call, Throwable t) { + callback.failure(t.getMessage()); + } + }); + } + + public Organisation[] getAllOrganisations() throws IOException { + Call> call = mispRestService.getAllOrganisations(); + Response> response = call.execute(); + + List 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>() { +// @Override +// public void onResponse(Call> call, Response> response) { +// if (!response.isSuccessful()) { +// // TODO handle +// return; +// } +// +// List 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> 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 call = mispRestService.addOrganisation(organisation); + + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (!response.isSuccessful()) { + callback.failure(extractError(response)); + } else { + assert response.body() != null; + callback.success(response.body().organisation); + } + } + + @Override + public void onFailure(Call 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> call = mispRestService.getServers(); + + call.enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (!response.isSuccessful()) { + callback.failure(extractError(response)); + } else { + callback.success(response.body()); + } + } + + @Override + public void onFailure(Call> 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 call = mispRestService.addServer(server); + + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (!response.isSuccessful()) { + callback.failure(extractError(response)); + } else { + callback.success(response.body()); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + callback.failure(t.getMessage()); + } + }); + } + + // error parsing + + /** + * Converts error {@link Response}s to human readable info. + * @param response erroneous response + * @param type of response + * @return human readable String that describes the error + */ + private String extractError(Response 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 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(); + } +} \ No newline at end of file diff --git a/app/src/main/java/lu/circl/mispbump/restful_client/MispRestService.java b/app/src/main/java/lu/circl/mispbump/restful_client/MispRestService.java new file mode 100644 index 0000000..4eb6430 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/restful_client/MispRestService.java @@ -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 pyMispVersion(); + + // user routes + + @GET("users/view/me") + Call getMyUserInformation(); + + @GET("users/view/{value}") + Call getUser(@Path("value") int userId); + + @POST("admin/users/add") + Call addUser(@Body User user); + + // organisation routes + + @GET("organisations/view/{value}") + Call getOrganisation(@Path("value") int orgId); + + @GET("organisations") + Call> getAllOrganisations(); + + @POST("admin/organisations/add") + Call addOrganisation(@Body Organisation organisation); + + // server routes + + @GET("servers/index") + Call> getServers(); + +// @POST("servers/add") +// Call addServer(@Body MispServer server); + + @POST("servers/add") + Call addServer(@Body Server server); +} \ No newline at end of file diff --git a/app/src/main/java/lu/circl/mispbump/restful_client/MispServer.java b/app/src/main/java/lu/circl/mispbump/restful_client/MispServer.java new file mode 100644 index 0000000..1c7b9b3 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/restful_client/MispServer.java @@ -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; + +} \ No newline at end of file diff --git a/app/src/main/java/lu/circl/mispbump/restful_client/MispUser.java b/app/src/main/java/lu/circl/mispbump/restful_client/MispUser.java new file mode 100644 index 0000000..4bcb275 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/restful_client/MispUser.java @@ -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; + } +} \ No newline at end of file diff --git a/app/src/main/java/lu/circl/mispbump/restful_client/Organisation.java b/app/src/main/java/lu/circl/mispbump/restful_client/Organisation.java new file mode 100644 index 0000000..f24bd59 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/restful_client/Organisation.java @@ -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 + + '}'; + } +} diff --git a/app/src/main/java/lu/circl/mispbump/restful_client/Server.java b/app/src/main/java/lu/circl/mispbump/restful_client/Server.java new file mode 100644 index 0000000..99ba9ad --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/restful_client/Server.java @@ -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 + + '}'; + } +} diff --git a/app/src/main/java/lu/circl/mispbump/restful_client/User.java b/app/src/main/java/lu/circl/mispbump/restful_client/User.java new file mode 100644 index 0000000..4535419 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/restful_client/User.java @@ -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 + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/app/src/main/java/lu/circl/mispbump/restful_client/Version.java b/app/src/main/java/lu/circl/mispbump/restful_client/Version.java new file mode 100644 index 0000000..5ba921e --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/restful_client/Version.java @@ -0,0 +1,10 @@ +package lu.circl.mispbump.restful_client; + +import com.google.gson.annotations.SerializedName; + +public class Version { + + @SerializedName("version") + public String version; + +} diff --git a/app/src/main/java/lu/circl/mispbump/security/DiffieHellman.java b/app/src/main/java/lu/circl/mispbump/security/DiffieHellman.java new file mode 100644 index 0000000..407b057 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/security/DiffieHellman.java @@ -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)); + } +} diff --git a/app/src/main/java/lu/circl/mispbump/security/KeyStoreWrapper.java b/app/src/main/java/lu/circl/mispbump/security/KeyStoreWrapper.java new file mode 100644 index 0000000..0576074 --- /dev/null +++ b/app/src/main/java/lu/circl/mispbump/security/KeyStoreWrapper.java @@ -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 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; + } +} \ No newline at end of file diff --git a/app/src/main/res/anim/fade_in.xml b/app/src/main/res/anim/fade_in.xml new file mode 100644 index 0000000..271b405 --- /dev/null +++ b/app/src/main/res/anim/fade_in.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/fade_out.xml b/app/src/main/res/anim/fade_out.xml new file mode 100644 index 0000000..b0caf35 --- /dev/null +++ b/app/src/main/res/anim/fade_out.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_down.xml b/app/src/main/res/anim/slide_down.xml deleted file mode 100644 index ad40deb..0000000 --- a/app/src/main/res/anim/slide_down.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/anim/slide_in_right.xml b/app/src/main/res/anim/slide_in_right.xml new file mode 100644 index 0000000..6fb52a3 --- /dev/null +++ b/app/src/main/res/anim/slide_in_right.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_out_left.xml b/app/src/main/res/anim/slide_out_left.xml new file mode 100644 index 0000000..020a1be --- /dev/null +++ b/app/src/main/res/anim/slide_out_left.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_up.xml b/app/src/main/res/anim/slide_up.xml deleted file mode 100644 index a731689..0000000 --- a/app/src/main/res/anim/slide_up.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/animator/rotation_cw.xml b/app/src/main/res/animator/rotation_cw.xml new file mode 100644 index 0000000..fdc9066 --- /dev/null +++ b/app/src/main/res/animator/rotation_cw.xml @@ -0,0 +1,10 @@ + + \ No newline at end of file diff --git a/app/src/main/res/animator/slide_in_left.xml b/app/src/main/res/animator/slide_in_left.xml new file mode 100644 index 0000000..ddc7c45 --- /dev/null +++ b/app/src/main/res/animator/slide_in_left.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/animator/slide_in_right.xml b/app/src/main/res/animator/slide_in_right.xml new file mode 100644 index 0000000..206720e --- /dev/null +++ b/app/src/main/res/animator/slide_in_right.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/animator/slide_out_left.xml b/app/src/main/res/animator/slide_out_left.xml new file mode 100644 index 0000000..e3e2d1d --- /dev/null +++ b/app/src/main/res/animator/slide_out_left.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/animator/slide_out_right.xml b/app/src/main/res/animator/slide_out_right.xml new file mode 100644 index 0000000..10d8add --- /dev/null +++ b/app/src/main/res/animator/slide_out_right.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml index 789e157..c7bd21d 100644 --- a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -1,34 +1,34 @@ - - - - - - - - - + xmlns:aapt="http://schemas.android.com/aapt" + android:width="108dp" + android:height="108dp" + android:viewportHeight="108" + android:viewportWidth="108"> + + + + + + + + + diff --git a/app/src/main/res/drawable/animated_sync.xml b/app/src/main/res/drawable/animated_sync.xml new file mode 100644 index 0000000..575c7bb --- /dev/null +++ b/app/src/main/res/drawable/animated_sync.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bottom_navigation_color_states.xml b/app/src/main/res/drawable/bottom_navigation_color_states.xml new file mode 100644 index 0000000..dde925c --- /dev/null +++ b/app/src/main/res/drawable/bottom_navigation_color_states.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_account_circle.xml b/app/src/main/res/drawable/ic_account_circle.xml new file mode 100644 index 0000000..e14b3e0 --- /dev/null +++ b/app/src/main/res/drawable/ic_account_circle.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/icon_arrow_right.xml b/app/src/main/res/drawable/ic_add.xml similarity index 63% rename from app/src/main/res/drawable/icon_arrow_right.xml rename to app/src/main/res/drawable/ic_add.xml index 25fb386..e3979cd 100644 --- a/app/src/main/res/drawable/icon_arrow_right.xml +++ b/app/src/main/res/drawable/ic_add.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/drawable/ic_arrow_back.xml b/app/src/main/res/drawable/ic_arrow_back.xml new file mode 100644 index 0000000..f8ad49c --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_back.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_forward.xml b/app/src/main/res/drawable/ic_arrow_forward.xml new file mode 100644 index 0000000..42d364a --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_forward.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_autorenew.xml b/app/src/main/res/drawable/ic_autorenew.xml new file mode 100644 index 0000000..3bdee48 --- /dev/null +++ b/app/src/main/res/drawable/ic_autorenew.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_bank_note.xml b/app/src/main/res/drawable/ic_bank_note.xml new file mode 100644 index 0000000..61b826e --- /dev/null +++ b/app/src/main/res/drawable/ic_bank_note.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/ic_check.xml b/app/src/main/res/drawable/ic_check.xml new file mode 100644 index 0000000..d04a04c --- /dev/null +++ b/app/src/main/res/drawable/ic_check.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_check_outline.xml b/app/src/main/res/drawable/ic_check_outline.xml new file mode 100644 index 0000000..9e25fdc --- /dev/null +++ b/app/src/main/res/drawable/ic_check_outline.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_circuit_board.xml b/app/src/main/res/drawable/ic_circuit_board.xml new file mode 100644 index 0000000..fb1d101 --- /dev/null +++ b/app/src/main/res/drawable/ic_circuit_board.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml new file mode 100644 index 0000000..cbf794b --- /dev/null +++ b/app/src/main/res/drawable/ic_close.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/icon_cloud_download.xml b/app/src/main/res/drawable/ic_cloud_download_black_24dp.xml similarity index 100% rename from app/src/main/res/drawable/icon_cloud_download.xml rename to app/src/main/res/drawable/ic_cloud_download_black_24dp.xml diff --git a/app/src/main/res/drawable/icon_cloud.xml b/app/src/main/res/drawable/ic_cloud_download_light_24dp.xml similarity index 87% rename from app/src/main/res/drawable/icon_cloud.xml rename to app/src/main/res/drawable/ic_cloud_download_light_24dp.xml index 0f28023..0feb270 100644 --- a/app/src/main/res/drawable/icon_cloud.xml +++ b/app/src/main/res/drawable/ic_cloud_download_light_24dp.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/drawable/ic_cloud_upload.xml b/app/src/main/res/drawable/ic_cloud_upload.xml new file mode 100644 index 0000000..55dbbae --- /dev/null +++ b/app/src/main/res/drawable/ic_cloud_upload.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_code_black_24dp.xml b/app/src/main/res/drawable/ic_code_black_24dp.xml new file mode 100644 index 0000000..5817fac --- /dev/null +++ b/app/src/main/res/drawable/ic_code_black_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_delete_forever.xml b/app/src/main/res/drawable/ic_delete_forever.xml new file mode 100644 index 0000000..4469a5e --- /dev/null +++ b/app/src/main/res/drawable/ic_delete_forever.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_description.xml b/app/src/main/res/drawable/ic_description.xml new file mode 100644 index 0000000..302bb4a --- /dev/null +++ b/app/src/main/res/drawable/ic_description.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_domain.xml b/app/src/main/res/drawable/ic_domain.xml new file mode 100644 index 0000000..b7e6b25 --- /dev/null +++ b/app/src/main/res/drawable/ic_domain.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/icon_add.xml b/app/src/main/res/drawable/ic_email_black_24dp.xml similarity index 61% rename from app/src/main/res/drawable/icon_add.xml rename to app/src/main/res/drawable/ic_email_black_24dp.xml index 0258249..ce97ab8 100644 --- a/app/src/main/res/drawable/icon_add.xml +++ b/app/src/main/res/drawable/ic_email_black_24dp.xml @@ -5,5 +5,5 @@ android:viewportHeight="24.0"> + android:pathData="M20,4L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM20,8l-8,5 -8,-5L4,6l8,5 8,-5v2z"/> diff --git a/app/src/main/res/drawable/ic_error_outline.xml b/app/src/main/res/drawable/ic_error_outline.xml new file mode 100644 index 0000000..93db179 --- /dev/null +++ b/app/src/main/res/drawable/ic_error_outline.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_help_outline.xml b/app/src/main/res/drawable/ic_help_outline.xml new file mode 100644 index 0000000..fc62825 --- /dev/null +++ b/app/src/main/res/drawable/ic_help_outline.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_info_outline.xml b/app/src/main/res/drawable/ic_info_outline.xml new file mode 100644 index 0000000..af0d4d0 --- /dev/null +++ b/app/src/main/res/drawable/ic_info_outline.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_key.xml b/app/src/main/res/drawable/ic_key.xml new file mode 100644 index 0000000..ae7aa7b --- /dev/null +++ b/app/src/main/res/drawable/ic_key.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_keyboard_arrow_up.xml b/app/src/main/res/drawable/ic_keyboard_arrow_up.xml new file mode 100644 index 0000000..bc01039 --- /dev/null +++ b/app/src/main/res/drawable/ic_keyboard_arrow_up.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_language.xml b/app/src/main/res/drawable/ic_language.xml new file mode 100644 index 0000000..74bc279 --- /dev/null +++ b/app/src/main/res/drawable/ic_language.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml index 1fa43f1..2408e30 100644 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -1,74 +1,74 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:height="108dp" + android:width="108dp" + android:viewportHeight="108" + android:viewportWidth="108" + xmlns:android="http://schemas.android.com/apk/res/android"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_location.xml b/app/src/main/res/drawable/ic_location.xml new file mode 100644 index 0000000..7ce2348 --- /dev/null +++ b/app/src/main/res/drawable/ic_location.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_open_in_browser.xml b/app/src/main/res/drawable/ic_open_in_browser.xml new file mode 100644 index 0000000..3fb9799 --- /dev/null +++ b/app/src/main/res/drawable/ic_open_in_browser.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_person.xml b/app/src/main/res/drawable/ic_person.xml new file mode 100644 index 0000000..f0aedfe --- /dev/null +++ b/app/src/main/res/drawable/ic_person.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_polka_dots.xml b/app/src/main/res/drawable/ic_polka_dots.xml new file mode 100644 index 0000000..30d036f --- /dev/null +++ b/app/src/main/res/drawable/ic_polka_dots.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable/ic_qrcode.xml b/app/src/main/res/drawable/ic_qrcode.xml new file mode 100644 index 0000000..b1092e9 --- /dev/null +++ b/app/src/main/res/drawable/ic_qrcode.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_qrcode_scan.xml b/app/src/main/res/drawable/ic_qrcode_scan.xml new file mode 100644 index 0000000..d740f75 --- /dev/null +++ b/app/src/main/res/drawable/ic_qrcode_scan.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_sector.xml b/app/src/main/res/drawable/ic_sector.xml new file mode 100644 index 0000000..eb94181 --- /dev/null +++ b/app/src/main/res/drawable/ic_sector.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml new file mode 100644 index 0000000..1397d37 --- /dev/null +++ b/app/src/main/res/drawable/ic_settings.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_sync_black_24dp.xml b/app/src/main/res/drawable/ic_sync_black_24dp.xml new file mode 100644 index 0000000..dd197a6 --- /dev/null +++ b/app/src/main/res/drawable/ic_sync_black_24dp.xml @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_wiggle.xml b/app/src/main/res/drawable/ic_wiggle.xml new file mode 100644 index 0000000..0e3e6fa --- /dev/null +++ b/app/src/main/res/drawable/ic_wiggle.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/icon_check.xml b/app/src/main/res/drawable/icon_check.xml deleted file mode 100644 index 459aef9..0000000 --- a/app/src/main/res/drawable/icon_check.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/icon_close.xml b/app/src/main/res/drawable/icon_close.xml deleted file mode 100644 index ede4b71..0000000 --- a/app/src/main/res/drawable/icon_close.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/icon_cloud_upload.xml b/app/src/main/res/drawable/icon_cloud_upload.xml deleted file mode 100644 index 7d0637d..0000000 --- a/app/src/main/res/drawable/icon_cloud_upload.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/icon_contact.xml b/app/src/main/res/drawable/icon_contact.xml deleted file mode 100644 index 0d6b3bb..0000000 --- a/app/src/main/res/drawable/icon_contact.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/icon_forward.xml b/app/src/main/res/drawable/icon_forward.xml deleted file mode 100644 index cf9e208..0000000 --- a/app/src/main/res/drawable/icon_forward.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/icon_hour_glass.xml b/app/src/main/res/drawable/icon_hour_glass.xml deleted file mode 100644 index fa7abc6..0000000 --- a/app/src/main/res/drawable/icon_hour_glass.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/icon_key.xml b/app/src/main/res/drawable/icon_key.xml deleted file mode 100644 index 2eddd16..0000000 --- a/app/src/main/res/drawable/icon_key.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/icon_retry.xml b/app/src/main/res/drawable/icon_retry.xml deleted file mode 100644 index 794ca6a..0000000 --- a/app/src/main/res/drawable/icon_retry.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/icon_round_check.xml b/app/src/main/res/drawable/icon_round_check.xml deleted file mode 100644 index 529c91e..0000000 --- a/app/src/main/res/drawable/icon_round_check.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/icon_round_error.xml b/app/src/main/res/drawable/icon_round_error.xml deleted file mode 100644 index cb3b30a..0000000 --- a/app/src/main/res/drawable/icon_round_error.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/icon_settings.xml b/app/src/main/res/drawable/icon_settings.xml deleted file mode 100644 index ce997a7..0000000 --- a/app/src/main/res/drawable/icon_settings.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/rounded_rect.xml b/app/src/main/res/drawable/rounded_rect.xml new file mode 100644 index 0000000..8daefbc --- /dev/null +++ b/app/src/main/res/drawable/rounded_rect.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rounded_square.xml b/app/src/main/res/drawable/rounded_square.xml deleted file mode 100644 index e042271..0000000 --- a/app/src/main/res/drawable/rounded_square.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml new file mode 100644 index 0000000..cb08ed5 --- /dev/null +++ b/app/src/main/res/layout/activity_home.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml new file mode 100644 index 0000000..1cb59dc --- /dev/null +++ b/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index 3dee13c..0000000 --- a/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/activity_my_organisation.xml b/app/src/main/res/layout/activity_my_organisation.xml deleted file mode 100644 index b0b60b6..0000000 --- a/app/src/main/res/layout/activity_my_organisation.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_preference.xml b/app/src/main/res/layout/activity_preference.xml new file mode 100644 index 0000000..b471d0f --- /dev/null +++ b/app/src/main/res/layout/activity_preference.xml @@ -0,0 +1,21 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_profile.xml b/app/src/main/res/layout/activity_profile.xml new file mode 100644 index 0000000..f39fdea --- /dev/null +++ b/app/src/main/res/layout/activity_profile.xml @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_qr_sync.xml b/app/src/main/res/layout/activity_qr_sync.xml deleted file mode 100644 index f2a5e48..0000000 --- a/app/src/main/res/layout/activity_qr_sync.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_sync.xml b/app/src/main/res/layout/activity_sync.xml new file mode 100644 index 0000000..6c823e7 --- /dev/null +++ b/app/src/main/res/layout/activity_sync.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_upload.xml b/app/src/main/res/layout/activity_upload.xml index bba536e..6574145 100644 --- a/app/src/main/res/layout/activity_upload.xml +++ b/app/src/main/res/layout/activity_upload.xml @@ -1,74 +1,90 @@ + - + - + + - + - + - + - - + - + - + - + - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/bottom_sheet_public_key.xml b/app/src/main/res/layout/bottom_sheet_public_key.xml new file mode 100644 index 0000000..66e4777 --- /dev/null +++ b/app/src/main/res/layout/bottom_sheet_public_key.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/bottom_sheet_sync_information.xml b/app/src/main/res/layout/bottom_sheet_sync_information.xml new file mode 100644 index 0000000..5d2c2d0 --- /dev/null +++ b/app/src/main/res/layout/bottom_sheet_sync_information.xml @@ -0,0 +1,23 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_enter_credentials.xml b/app/src/main/res/layout/dialog_enter_credentials.xml deleted file mode 100644 index 20d7bf6..0000000 --- a/app/src/main/res/layout/dialog_enter_credentials.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_public_key.xml b/app/src/main/res/layout/dialog_public_key.xml deleted file mode 100644 index 54122d8..0000000 --- a/app/src/main/res/layout/dialog_public_key.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_save_authkey.xml b/app/src/main/res/layout/dialog_save_authkey.xml deleted file mode 100644 index fd9e56f..0000000 --- a/app/src/main/res/layout/dialog_save_authkey.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_select_delete_data.xml b/app/src/main/res/layout/dialog_select_delete_data.xml deleted file mode 100644 index 82c7388..0000000 --- a/app/src/main/res/layout/dialog_select_delete_data.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_sync_info.xml b/app/src/main/res/layout/dialog_sync_info.xml deleted file mode 100644 index 133dad2..0000000 --- a/app/src/main/res/layout/dialog_sync_info.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_camera.xml b/app/src/main/res/layout/fragment_camera.xml index b1233a8..7bd5ce8 100644 --- a/app/src/main/res/layout/fragment_camera.xml +++ b/app/src/main/res/layout/fragment_camera.xml @@ -1,18 +1,28 @@ - + - + - \ No newline at end of file + + + diff --git a/app/src/main/res/layout/fragment_sync_options.xml b/app/src/main/res/layout/fragment_sync_options.xml new file mode 100644 index 0000000..7b32505 --- /dev/null +++ b/app/src/main/res/layout/fragment_sync_options.xml @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/material_preference_text.xml b/app/src/main/res/layout/material_preference_text.xml new file mode 100644 index 0000000..cc188b5 --- /dev/null +++ b/app/src/main/res/layout/material_preference_text.xml @@ -0,0 +1,48 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/row_org_info_entry.xml b/app/src/main/res/layout/row_org_info_entry.xml deleted file mode 100644 index 5a244f5..0000000 --- a/app/src/main/res/layout/row_org_info_entry.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/row_synced_organisation.xml b/app/src/main/res/layout/row_synced_organisation.xml deleted file mode 100644 index daaa5bf..0000000 --- a/app/src/main/res/layout/row_synced_organisation.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/row_upload_state.xml b/app/src/main/res/layout/row_upload_state.xml deleted file mode 100644 index f188423..0000000 --- a/app/src/main/res/layout/row_upload_state.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/view_organisation.xml b/app/src/main/res/layout/view_organisation.xml deleted file mode 100644 index 2564feb..0000000 --- a/app/src/main/res/layout/view_organisation.xml +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/view_pk_info.xml b/app/src/main/res/layout/view_pk_info.xml deleted file mode 100644 index 0bbdef9..0000000 --- a/app/src/main/res/layout/view_pk_info.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/view_upload_action.xml b/app/src/main/res/layout/view_upload_action.xml new file mode 100644 index 0000000..9d88e7c --- /dev/null +++ b/app/src/main/res/layout/view_upload_action.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/viewholder_sync.xml b/app/src/main/res/layout/viewholder_sync.xml new file mode 100644 index 0000000..2d262e7 --- /dev/null +++ b/app/src/main/res/layout/viewholder_sync.xml @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/viewholder_user.xml b/app/src/main/res/layout/viewholder_user.xml new file mode 100644 index 0000000..f3950c3 --- /dev/null +++ b/app/src/main/res/layout/viewholder_user.xml @@ -0,0 +1,43 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/main_menu.xml b/app/src/main/res/menu/main_menu.xml new file mode 100644 index 0000000..3defc8b --- /dev/null +++ b/app/src/main/res/menu/main_menu.xml @@ -0,0 +1,16 @@ + +

+ + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_login.xml b/app/src/main/res/menu/menu_login.xml new file mode 100644 index 0000000..83ee6ad --- /dev/null +++ b/app/src/main/res/menu/menu_login.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml deleted file mode 100644 index e49f064..0000000 --- a/app/src/main/res/menu/menu_main.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/menu/menu_my_org.xml b/app/src/main/res/menu/menu_my_org.xml deleted file mode 100644 index 97841d1..0000000 --- a/app/src/main/res/menu/menu_my_org.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/menu/menu_profile.xml b/app/src/main/res/menu/menu_profile.xml new file mode 100644 index 0000000..8388c42 --- /dev/null +++ b/app/src/main/res/menu/menu_profile.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_sync.xml b/app/src/main/res/menu/menu_sync.xml new file mode 100644 index 0000000..7d4e56e --- /dev/null +++ b/app/src/main/res/menu/menu_sync.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index db69e7e..bbd3e02 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index db69e7e..bbd3e02 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png index a2f5908..898f3ed 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png index 1b52399..dffca36 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-hdpi/launcher_handshake_round.png b/app/src/main/res/mipmap-hdpi/launcher_handshake_round.png deleted file mode 100644 index aa2f41a..0000000 Binary files a/app/src/main/res/mipmap-hdpi/launcher_handshake_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-hdpi/launcher_handshake_square.png b/app/src/main/res/mipmap-hdpi/launcher_handshake_square.png deleted file mode 100644 index 7d50fb2..0000000 Binary files a/app/src/main/res/mipmap-hdpi/launcher_handshake_square.png and /dev/null differ diff --git a/app/src/main/res/mipmap-hdpi/launcher_round_2.png b/app/src/main/res/mipmap-hdpi/launcher_round_2.png deleted file mode 100644 index c41209a..0000000 Binary files a/app/src/main/res/mipmap-hdpi/launcher_round_2.png and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png index ff10afd..64ba76f 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png index 115a4c7..dae5e08 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/launcher_handshake_round.png b/app/src/main/res/mipmap-mdpi/launcher_handshake_round.png deleted file mode 100644 index 8d1c20c..0000000 Binary files a/app/src/main/res/mipmap-mdpi/launcher_handshake_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/launcher_handshake_square.png b/app/src/main/res/mipmap-mdpi/launcher_handshake_square.png deleted file mode 100644 index b667a85..0000000 Binary files a/app/src/main/res/mipmap-mdpi/launcher_handshake_square.png and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/launcher_round_2.png b/app/src/main/res/mipmap-mdpi/launcher_round_2.png deleted file mode 100644 index 6eea0a2..0000000 Binary files a/app/src/main/res/mipmap-mdpi/launcher_round_2.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png index dcd3cd8..e5ed465 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png index 459ca60..14ed0af 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/launcher_handshake_round.png b/app/src/main/res/mipmap-xhdpi/launcher_handshake_round.png deleted file mode 100644 index 8fbf9bb..0000000 Binary files a/app/src/main/res/mipmap-xhdpi/launcher_handshake_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/launcher_handshake_square.png b/app/src/main/res/mipmap-xhdpi/launcher_handshake_square.png deleted file mode 100644 index fa562a7..0000000 Binary files a/app/src/main/res/mipmap-xhdpi/launcher_handshake_square.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/launcher_round_2.png b/app/src/main/res/mipmap-xhdpi/launcher_round_2.png deleted file mode 100644 index e318340..0000000 Binary files a/app/src/main/res/mipmap-xhdpi/launcher_round_2.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 8ca12fe..b0907ca 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png index 8e19b41..d8ae031 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/launcher_handshake_round.png b/app/src/main/res/mipmap-xxhdpi/launcher_handshake_round.png deleted file mode 100644 index 54659ec..0000000 Binary files a/app/src/main/res/mipmap-xxhdpi/launcher_handshake_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/launcher_handshake_square.png b/app/src/main/res/mipmap-xxhdpi/launcher_handshake_square.png deleted file mode 100644 index 1ba2b77..0000000 Binary files a/app/src/main/res/mipmap-xxhdpi/launcher_handshake_square.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/launcher_round_2.png b/app/src/main/res/mipmap-xxhdpi/launcher_round_2.png deleted file mode 100644 index d440e89..0000000 Binary files a/app/src/main/res/mipmap-xxhdpi/launcher_round_2.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index b824ebd..2c18de9 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png index 4c19a13..beed3cd 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/launcher_handshake_round.png b/app/src/main/res/mipmap-xxxhdpi/launcher_handshake_round.png deleted file mode 100644 index 92de8dc..0000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/launcher_handshake_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/launcher_handshake_square.png b/app/src/main/res/mipmap-xxxhdpi/launcher_handshake_square.png deleted file mode 100644 index 83ba86f..0000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/launcher_handshake_square.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/launcher_round_2.png b/app/src/main/res/mipmap-xxxhdpi/launcher_round_2.png deleted file mode 100644 index e0f8838..0000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/launcher_round_2.png and /dev/null differ diff --git a/app/src/main/res/raw/misp.crt b/app/src/main/res/raw/misp.crt new file mode 100644 index 0000000..00f458a --- /dev/null +++ b/app/src/main/res/raw/misp.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIUEHRo5YZyVzJRWcqG4zHzG68DoTgwDQYJKoZIhvcNAQEL +BQAwcTELMAkGA1UEBhMCREUxDDAKBgNVBAgMA05SVzENMAsGA1UEBwwEQm9ubjEO +MAwGA1UECgwFRmVsaXgxEjAQBgNVBAMMCWxvY2FsaG9zdDEhMB8GCSqGSIb3DQEJ +ARYSZmVsaXhwa0BvdXRsb29rLmRlMB4XDTE5MDUxMzA4NTEwOVoXDTIwMDUxMjA4 +NTEwOVowcTELMAkGA1UEBhMCREUxDDAKBgNVBAgMA05SVzENMAsGA1UEBwwEQm9u +bjEOMAwGA1UECgwFRmVsaXgxEjAQBgNVBAMMCWxvY2FsaG9zdDEhMB8GCSqGSIb3 +DQEJARYSZmVsaXhwa0BvdXRsb29rLmRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEA0ut7qAzPuNPoZmQNCyOkDL8szBT6+pspGNQHQpL7GG5UotlniXcp +q+FtF4X4PEow5P0aRMBG72QIxUzAtFiqK2/RCbK3ECrKezedM26BT0xkEB/eUU7E +RQIO+XYa4JUi+g+YwnDCnPWexGIsztq+vvHXdny+uMeRCBFQxiPuPwdB4uPQyy8d +kv9XNScRCOu4Hp4IaFBIw5V7uP71WdyoHjD7NBbzubrVjcr+I0DC+MbpJOKpM/YB +lbp+I9NeA/rWZ4r6rrMAVOv6tV2dgnQ+6cWOFBiM3ZkxuocPWg/iI4UzdSsy2K/W +qONJfAjAqkmgkBBr2cyW5wwWEN8J994DIwIDAQABo1MwUTAdBgNVHQ4EFgQUWsMY +gtEo2b2WtuROWMfRDKzwTT0wHwYDVR0jBBgwFoAUWsMYgtEo2b2WtuROWMfRDKzw +TT0wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAmGU2K9isN1Wm +2PksR92pKrs7SJom2L3o7V3ui8oZ2FZrLx/IUeUA4aSmSeyqbxuziPqYUkO0ku1t +9ASaf9yaNS7CULDftgoebLzlIEwktvEI3XixpRIfJJywHSG5eLsG51D65hyuOZ2z +s0Y94HzvKX4fIaXv7NxGCW+xHhc6anxYKrXFldFwv3z/NHq0pzvd/aebbfuEeggH +xpmA3dgi0y0meTLKzYzDypEhkUPiq6u8R+cEuFgSun89/fW80VktEo+32tkaczEm +qjcZGfUFGNGG08p6MfRSl9PSakpdymV+aKK+TxB5nACe/RmISkqLz9REDlzUyNDm +yArY8SEbhg== +-----END CERTIFICATE----- diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index b0f7753..11144ba 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1,52 +1,14 @@ - MISPBump - - Verlassen - Zurück - Sie haben bisher keine Instanzen verknüpft - Keine lokalen Informationen vorhanden - Autorisierungschlüssel speichern (potentielles Sicherheitsrisiko) - Ungesicherte Änderungen - Wollen Sie die Änderungen sichern? - Speichern - Verwerfen - Lokale Informationen fehlen - Bitte laden Sie erst die fehlenden Informationen von Ihrer Instanz - Informationen Laden - Hochladen - Akzeptieren - Ablehnen - Öffentlicher Schlüssel - QR Code - weiter - Löschen - Lokale Daten löschen - Die URL, der Autorisierungsschlüssel und ihre lokalen Daten werden gelöscht. \n\nHinweis: diese Informationen sind für eine Synchronissierung notwendig - - Überschreiben - Lokale Daten überschreiben - Wollen Sie wirklich die lokalen Daten auf diesem Gerät überschreiben? - - Synchronisierungs Profil - - Server URL - Autorisierungsschlüssel - - Autentifizierungseinstellungen - - Sync Informationen - - MISP Url benötigt - MISP Automatisierungsschlüssel benötigt - In Zwischenablage kopiert - %1$s im Browser öffnen? - Im Browser öffnen - öffnen - Einstellungen - Wurde Ihr Öffentlicher Schlüssel bereits von Ihrem Partner gescannt? - Wurden Ihre Synchronisations Informationen bereits von Ihrem Partner gescannt? - Ja - Nein - Fortfahren + MispBump + Anmelden + Abmelden + Hilfe + MISP Automatisierungs-Schlüssel + Keine Informationen + Automatisierungs-Schlüssel speichern + MISP Server URL\nDie URL unter der Ihre MISP Instanz erreichbar ist.\n\nMISP Automatisierungs Schlüssel\nZu finden unter ... + QR code + Synchronisation + Sie haben noch keine MISP Instanzen verknüpft. \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml new file mode 100644 index 0000000..1fa0848 --- /dev/null +++ b/app/src/main/res/values/attrs.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 5e41eb5..cc2e9fc 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,11 +1,17 @@ - #ffffff - #424242 - #212121 - #42a5f5 - #AA42a5f5 - #FFF - #00c853 - #BB00c853 + #047EB4 + #023850 + + #12B3FA + #8012B3FA + + #33000000 + + + #FFFFFF + #80FFFFFF + #4CAF50 + #FB8C00 + #E53935 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 741b754..d881361 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,56 +1,16 @@ - MISPBump - - Exit - Next - Back - - You have not synced any instances yet - No local information available - Save auth-key (potential security risk) - Unsaved changes - Do you want to save the changes? - Save - Discard - Missing local information - Please fetch the missing information from your instance first - Fetch information - Upload - Accept - Reject - Public Key - QR code - continue - delete - Delete local data - - This action will delete the URL, automation key and your downloaded organisation information. - \n\nKeep in mind that the latter is required for synchronisation. - - override - Override local data - Do you really want to override the local information stored on this device? - Sync Profile - Server URL - Authkey - Credential Settings - Sync Information - - Enter MISP base url - Enter MISP automation key - Copied to clipboard - Open %1$s in browser? - Open in browser - open - Settings - Has your sync partner scanned your Public Key? - Has your sync partner scanned your Sync Information? - Yes - No - Proceed - - - Hello blank fragment - - + MispBump + Log in + Log out + MISP Server URL + MISP Automation Key + No Information + Save Automation Key + Home + Help + MISP Server URL\nPublic MISP URL\n\nMISP Automation key\nZu finden unter ... + Okay + QR code + Synchronization + You have not synced any MISP instances yet. diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 15ac006..3116eeb 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,24 +1,26 @@ + - + - + - + + diff --git a/app/src/test/java/de/overview/wg/its/mispbump/ExampleUnitTest.java b/app/src/test/java/lu/circl/mispbump/ExampleUnitTest.java similarity index 90% rename from app/src/test/java/de/overview/wg/its/mispbump/ExampleUnitTest.java rename to app/src/test/java/lu/circl/mispbump/ExampleUnitTest.java index 2ff2654..ef7a9dd 100644 --- a/app/src/test/java/de/overview/wg/its/mispbump/ExampleUnitTest.java +++ b/app/src/test/java/lu/circl/mispbump/ExampleUnitTest.java @@ -1,4 +1,4 @@ -package de.overview.wg.its.mispbump; +package lu.circl.mispbump; import org.junit.Test; diff --git a/build.gradle b/build.gradle index 909e1a2..e0e9fa2 100644 --- a/build.gradle +++ b/build.gradle @@ -1,14 +1,15 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - + ext.kotlin_version = '1.3.31' repositories { google() jcenter() + } - dependencies { - classpath 'com.android.tools.build:gradle:3.1.3' + classpath 'com.android.tools.build:gradle:3.4.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -19,8 +20,6 @@ allprojects { repositories { google() jcenter() - - maven { url "https://jitpack.io" } } } diff --git a/gradle.properties b/gradle.properties index 743d692..34470d8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,8 +6,12 @@ # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. +android.useAndroidX=true +android.enableJetifier=true org.gradle.jvmargs=-Xmx1536m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true + + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 01b8bf6..f6b961f 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 933b647..4e2d414 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Wed May 08 12:00:08 CEST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega