소스 검색

cleanup

prepare for further model change
pull/4/head
Felix Prahl-Kamps 1 년 전
부모
커밋
612615ff02
60개의 변경된 파일1956개의 추가작업 그리고 1525개의 파일을 삭제
  1. +116
    -0
      .idea/codeStyles/Project.xml
  2. +10
    -8
      app/build.gradle
  3. +17
    -20
      app/src/main/AndroidManifest.xml
  4. +22
    -50
      app/src/main/java/lu/circl/mispbump/activities/ExchangeActivity.java
  5. +16
    -42
      app/src/main/java/lu/circl/mispbump/activities/HomeActivity.java
  6. +2
    -2
      app/src/main/java/lu/circl/mispbump/activities/LauncherActivity.java
  7. +99
    -0
      app/src/main/java/lu/circl/mispbump/activities/NetworkTestActivity.java
  8. +11
    -23
      app/src/main/java/lu/circl/mispbump/activities/ProfileActivity.java
  9. +190
    -0
      app/src/main/java/lu/circl/mispbump/activities/SyncInfoDetailActivity.java
  10. +105
    -178
      app/src/main/java/lu/circl/mispbump/activities/UploadActivity.java
  11. +0
    -269
      app/src/main/java/lu/circl/mispbump/activities/UploadInfoActivity.java
  12. +99
    -0
      app/src/main/java/lu/circl/mispbump/adapters/SyncInfoAdapter.java
  13. +0
    -98
      app/src/main/java/lu/circl/mispbump/adapters/UploadInfoAdapter.java
  14. +6
    -6
      app/src/main/java/lu/circl/mispbump/auxiliary/DialogManager.java
  15. +20
    -15
      app/src/main/java/lu/circl/mispbump/auxiliary/MispRestClient.java
  16. +48
    -70
      app/src/main/java/lu/circl/mispbump/auxiliary/PreferenceManager.java
  17. +5
    -26
      app/src/main/java/lu/circl/mispbump/customViews/MaterialPasswordView.java
  18. +146
    -0
      app/src/main/java/lu/circl/mispbump/customViews/ProgressActionView.java
  19. +6
    -7
      app/src/main/java/lu/circl/mispbump/customViews/UploadAction.java
  20. +0
    -68
      app/src/main/java/lu/circl/mispbump/fragments/UploadCredentialsFragment.java
  21. +0
    -89
      app/src/main/java/lu/circl/mispbump/fragments/UploadSettingsFragment.java
  22. +1
    -1
      app/src/main/java/lu/circl/mispbump/interfaces/MispService.java
  23. +44
    -0
      app/src/main/java/lu/circl/mispbump/models/ExchangeInformation.java
  24. +102
    -13
      app/src/main/java/lu/circl/mispbump/models/SyncInformation.java
  25. +0
    -91
      app/src/main/java/lu/circl/mispbump/models/SyncModel.java
  26. +0
    -130
      app/src/main/java/lu/circl/mispbump/models/UploadInformation.java
  27. +24
    -27
      app/src/main/java/lu/circl/mispbump/models/restModels/Organisation.java
  28. +34
    -30
      app/src/main/java/lu/circl/mispbump/models/restModels/Server.java
  29. +35
    -24
      app/src/main/java/lu/circl/mispbump/models/restModels/User.java
  30. +1
    -1
      app/src/main/java/lu/circl/mispbump/security/KeyStoreWrapper.java
  31. +9
    -0
      app/src/main/res/drawable/ic_arrow_down.xml
  32. +9
    -0
      app/src/main/res/drawable/ic_cloud_download.xml
  33. +6
    -0
      app/src/main/res/drawable/tooltip_background.xml
  34. +8
    -0
      app/src/main/res/layout/activity_network_test.xml
  35. +289
    -0
      app/src/main/res/layout/activity_sync_info_detail.xml
  36. +57
    -58
      app/src/main/res/layout/activity_upload.xml
  37. +0
    -51
      app/src/main/res/layout/fragment_upload_credentials.xml
  38. +0
    -75
      app/src/main/res/layout/fragment_upload_settings.xml
  39. +2
    -14
      app/src/main/res/layout/material_password_view.xml
  40. +39
    -28
      app/src/main/res/layout/row_upload_information.xml
  41. +5
    -4
      app/src/main/res/layout/view_upload_action.xml
  42. +1
    -0
      app/src/main/res/values-de/strings.xml
  43. +5
    -2
      app/src/main/res/values/attrs.xml
  44. +4
    -0
      app/src/main/res/values/colors.xml
  45. +1
    -0
      app/src/main/res/values/strings.xml
  46. +2
    -2
      build.gradle
  47. +1
    -0
      expandablecardview/.gitignore
  48. +34
    -0
      expandablecardview/build.gradle
  49. +21
    -0
      expandablecardview/proguard-rules.pro
  50. +29
    -0
      expandablecardview/src/androidTest/java/lu/circl/expandablecardview/ExampleInstrumentedTest.java
  51. +1
    -0
      expandablecardview/src/main/AndroidManifest.xml
  52. +177
    -0
      expandablecardview/src/main/java/lu/circl/expandablecardview/ExpandableCardView.java
  53. +9
    -0
      expandablecardview/src/main/res/drawable/ic_info_outline.xml
  54. +9
    -0
      expandablecardview/src/main/res/drawable/ic_keyboard_arrow_down.xml
  55. +37
    -0
      expandablecardview/src/main/res/layout/expandable_card_view_header.xml
  56. +17
    -0
      expandablecardview/src/main/res/values/attrs.xml
  57. +3
    -0
      expandablecardview/src/main/res/values/strings.xml
  58. +19
    -0
      expandablecardview/src/test/java/lu/circl/expandablecardview/ExampleUnitTest.java
  59. +2
    -2
      gradle/wrapper/gradle-wrapper.properties
  60. +1
    -1
      settings.gradle

+ 116
- 0
.idea/codeStyles/Project.xml 파일 보기

@@ -0,0 +1,116 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<codeStyleSettings language="XML">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
</code_scheme>
</component>

+ 10
- 8
app/build.gradle 파일 보기

@@ -3,11 +3,11 @@ apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-android'

android {
compileSdkVersion 28
compileSdkVersion 29
defaultConfig {
applicationId "lu.circl.mispbump"
minSdkVersion 23
targetSdkVersion 28
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -26,6 +26,10 @@ android {
buildToolsVersion = '29.0.1'
}

repositories {
mavenCentral()
}

dependencies {
// android
implementation 'com.google.android.material:material:1.0.0'
@@ -37,9 +41,9 @@ dependencies {
implementation 'androidx.preference:preference:1.1.0-rc01'

// 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.squareup.retrofit2:retrofit:2.6.1'
implementation 'com.squareup.retrofit2:converter-gson:2.6.1'
implementation 'com.squareup.okhttp3:logging-interceptor:4.1.0'

// barcode reading
implementation 'com.google.android.gms:play-services-vision:18.0.0'
@@ -53,7 +57,5 @@ dependencies {
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()
implementation project(path: ':expandablecardview')
}

+ 17
- 20
app/src/main/AndroidManifest.xml 파일 보기

@@ -1,12 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="lu.circl.mispbump">

<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.INTERNET"/>

<application
android:allowBackup="false"
android:hardwareAccelerated="true"
@@ -16,43 +12,44 @@
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name=".activities.StartUpActivity">
<activity android:name=".activities.NetworkTestActivity"></activity>
<activity android:name=".activities.UploadActivity" />
<activity android:name=".activities.LauncherActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".activities.LoginActivity"
android:label="@string/login"/>
android:label="@string/login" />
<activity
android:name=".activities.HomeActivity"
android:label="@string/app_name"/>
android:label="@string/app_name" />
<activity
android:name=".activities.ExchangeActivity"
android:configChanges="orientation|screenSize"
android:parentActivityName=".activities.HomeActivity"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.Translucent"/>
<activity
android:name=".activities.UploadInfoActivity"
android:configChanges="orientation|screenSize"
android:parentActivityName=".activities.HomeActivity"/>
android:theme="@style/AppTheme.Translucent" />
<activity
android:name=".activities.UploadActivity"
android:name=".activities.SyncInfoDetailActivity"
android:configChanges="orientation|screenSize"
android:label="Upload"
android:parentActivityName=".activities.HomeActivity"/>
android:label="@string/sync_details_activity_label"
android:parentActivityName=".activities.HomeActivity" />
<activity
android:name=".activities.PreferenceActivity"
android:label="@string/settings"
android:parentActivityName=".activities.HomeActivity"/>
android:parentActivityName=".activities.HomeActivity" />
<activity
android:name=".activities.ProfileActivity"
android:label="Profile"
android:parentActivityName=".activities.HomeActivity"
android:theme="@style/AppTheme.Translucent"/>
android:theme="@style/AppTheme.Translucent" />
</application>
<uses-permission android:name="android.permission.CAMERA" />

<uses-permission android:name="android.permission.INTERNET" />

</manifest>

+ 22
- 50
app/src/main/java/lu/circl/mispbump/activities/ExchangeActivity.java 파일 보기

@@ -22,16 +22,14 @@ import com.google.gson.JsonSyntaxException;

import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.List;

import lu.circl.mispbump.R;
import lu.circl.mispbump.auxiliary.DialogManager;
import lu.circl.mispbump.auxiliary.PreferenceManager;
import lu.circl.mispbump.auxiliary.QrCodeGenerator;
import lu.circl.mispbump.auxiliary.RandomString;
import lu.circl.mispbump.fragments.CameraFragment;
import lu.circl.mispbump.models.ExchangeInformation;
import lu.circl.mispbump.models.SyncInformation;
import lu.circl.mispbump.models.UploadInformation;
import lu.circl.mispbump.models.restModels.Server;
import lu.circl.mispbump.security.DiffieHellman;


@@ -40,7 +38,6 @@ public class ExchangeActivity extends AppCompatActivity {
private PreferenceManager preferenceManager;
private QrCodeGenerator qrCodeGenerator;
private DiffieHellman diffieHellman;
private UploadInformation uploadInformation;

private CameraFragment cameraFragment;

@@ -50,6 +47,8 @@ public class ExchangeActivity extends AppCompatActivity {
private ImageView qrCode;
private ImageButton prevButton, nextButton;

private SyncInformation syncInformation;

private Bitmap publicKeyQr, dataQr;

private SyncState currentSyncState;
@@ -67,9 +66,10 @@ public class ExchangeActivity extends AppCompatActivity {
initViews();
initCamera();

uploadInformation = new UploadInformation();
publicKeyQr = generatePublicKeyBitmap();

syncInformation = new SyncInformation();

setSyncState(SyncState.KEY_EXCHANGE);
}

@@ -105,24 +105,23 @@ public class ExchangeActivity extends AppCompatActivity {
fragmentTransaction.commit();
}

private ExchangeInformation generateSyncExchangeInformation() {
ExchangeInformation exchangeInformation = new ExchangeInformation();
exchangeInformation.setOrganisation(preferenceManager.getUserOrganisation().toSyncOrganisation());
exchangeInformation.setSyncUser(preferenceManager.getUserInfo().toSyncUser());
exchangeInformation.setServer(new Server(preferenceManager.getUserCredentials().first));
return exchangeInformation;
}


private Bitmap generatePublicKeyBitmap() {
return qrCodeGenerator.generateQrCode(DiffieHellman.publicKeyToString(diffieHellman.getPublicKey()));
}

private Bitmap generateLocalSyncInfoBitmap() {
uploadInformation.setLocal(generateLocalSyncInfo());
return qrCodeGenerator.generateQrCode(diffieHellman.encrypt(new Gson().toJson(uploadInformation.getLocal())));
}

private SyncInformation generateLocalSyncInfo() {
SyncInformation syncInformation = new SyncInformation();
syncInformation.organisation = preferenceManager.getUserOrganisation().toSyncOrganisation();
syncInformation.syncUserAuthkey = new RandomString(40).nextString();
syncInformation.baseUrl = preferenceManager.getUserCredentials().first;
syncInformation.syncUserPassword = new RandomString(16).nextString();
syncInformation.syncUserEmail = preferenceManager.getUserInfo().getEmail();
return syncInformation;
ExchangeInformation exchangeInformation = generateSyncExchangeInformation();
syncInformation.setLocal(exchangeInformation);
return qrCodeGenerator.generateQrCode(diffieHellman.encrypt(new Gson().toJson(exchangeInformation)));
}


@@ -275,34 +274,9 @@ public class ExchangeActivity extends AppCompatActivity {
break;
case DATA_EXCHANGE:
try {
final SyncInformation remoteSyncInfo = new Gson().fromJson(diffieHellman.decrypt(qrData), SyncInformation.class);

final List<UploadInformation> uploadInformationList = preferenceManager.getUploadInformationList();

for (final UploadInformation ui : uploadInformationList) {
if (ui.getRemote().organisation.getUuid().equals(remoteSyncInfo.organisation.getUuid())) {
DialogManager.syncAlreadyExistsDialog(ui.getRemote(), remoteSyncInfo, ExchangeActivity.this, new DialogManager.IDialogFeedback() {
@Override
public void positive() {
// update remote info only
uploadInformation.setUuid(ui.getUuid());
uploadInformation.setDate();
}

@Override
public void negative() {
// replace credentials too
uploadInformationList.remove(ui);
preferenceManager.setUploadInformationList(uploadInformationList);
}
});

break;
}
}

uploadInformation.setRemote(remoteSyncInfo);
preferenceManager.addUploadInformation(uploadInformation);
ExchangeInformation remoteSyncInfo = new Gson().fromJson(diffieHellman.decrypt(qrData), ExchangeInformation.class);
syncInformation.populateRemoteExchangeInformation(remoteSyncInfo);
preferenceManager.addSyncInformation(syncInformation);
setSyncState(SyncState.DATA_EXCHANGE_DONE);
} catch (JsonSyntaxException e) {
if (currentReadQrStatus == ReadQrStatus.PENDING) {
@@ -340,10 +314,8 @@ public class ExchangeActivity extends AppCompatActivity {
setSyncState(SyncState.DATA_EXCHANGE);
break;
case DATA_EXCHANGE_DONE:
uploadInformation.setCurrentSyncStatus(UploadInformation.SyncStatus.PENDING);
preferenceManager.addUploadInformation(uploadInformation);
Intent i = new Intent(ExchangeActivity.this, UploadInfoActivity.class);
i.putExtra(UploadInfoActivity.EXTRA_UPLOAD_INFO_UUID, uploadInformation.getUuid());
Intent i = new Intent(ExchangeActivity.this, SyncInfoDetailActivity.class);
i.putExtra(SyncInfoDetailActivity.EXTRA_SYNC_INFO_UUID, syncInformation.getUuid());
startActivity(i);
finish();
break;


+ 16
- 42
app/src/main/java/lu/circl/mispbump/activities/HomeActivity.java 파일 보기

@@ -12,7 +12,6 @@ import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityOptionsCompat;
import androidx.core.util.Pair;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

@@ -21,21 +20,18 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton;
import java.util.List;

import lu.circl.mispbump.R;
import lu.circl.mispbump.adapters.UploadInfoAdapter;
import lu.circl.mispbump.auxiliary.MispRestClient;
import lu.circl.mispbump.adapters.SyncInfoAdapter;
import lu.circl.mispbump.auxiliary.PreferenceManager;
import lu.circl.mispbump.interfaces.OnRecyclerItemClickListener;
import lu.circl.mispbump.models.SyncModel;
import lu.circl.mispbump.models.UploadInformation;
import lu.circl.mispbump.models.restModels.Server;
import lu.circl.mispbump.models.SyncInformation;


public class HomeActivity extends AppCompatActivity {

private List<UploadInformation> uploadInformationList;
private List<SyncInformation> syncInformationList;
private PreferenceManager preferenceManager;
private RecyclerView recyclerView;
private UploadInfoAdapter uploadInfoAdapter;
private SyncInfoAdapter syncInfoAdapter;
private TextView emptyRecyclerView;

@Override
@@ -90,55 +86,33 @@ public class HomeActivity extends AppCompatActivity {
private void initRecyclerView() {
recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(HomeActivity.this));
uploadInfoAdapter = new UploadInfoAdapter(HomeActivity.this);
uploadInfoAdapter.setOnRecyclerPositionClickListener(onRecyclerItemClickListener());
recyclerView.setAdapter(uploadInfoAdapter);
syncInfoAdapter = new SyncInfoAdapter();
syncInfoAdapter.setOnRecyclerPositionClickListener(onRecyclerItemClickListener());
recyclerView.setAdapter(syncInfoAdapter);
}

private void refreshRecyclerView() {
syncInformationList = preferenceManager.getSyncInformationList();

Pair<String, String> credentials = preferenceManager.getUserCredentials();
MispRestClient mispRestClient = MispRestClient.getInstance(credentials.first, credentials.second);

mispRestClient.getAllServers(new MispRestClient.AllServersCallback() {
@Override
public void success(Server[] servers) {
SyncModel.createFromServer(mispRestClient, servers[0], new SyncModel.InitializeWithServerObject() {
@Override
public void success(SyncModel syncModel) {
Log.d("DEBUG", syncModel.toString());
}

@Override
public void failure(String error) {

}
});
}

@Override
public void failure(String error) {

}
});

uploadInformationList = preferenceManager.getUploadInformationList();

if (uploadInformationList.isEmpty()) {
if (syncInformationList.isEmpty()) {
emptyRecyclerView.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE);
} else {
emptyRecyclerView.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
uploadInfoAdapter.setItems(uploadInformationList);
syncInfoAdapter.setItems(syncInformationList);

for (SyncInformation si : syncInformationList) {
Log.d("DEBUG", si.toString());
}
}
}


private OnRecyclerItemClickListener<Integer> onRecyclerItemClickListener() {
return (v, index) -> {
Intent i = new Intent(HomeActivity.this, UploadInfoActivity.class);
i.putExtra(UploadInfoActivity.EXTRA_UPLOAD_INFO_UUID, uploadInformationList.get(index).getUuid());
Intent i = new Intent(HomeActivity.this, SyncInfoDetailActivity.class);
i.putExtra(SyncInfoDetailActivity.EXTRA_SYNC_INFO_UUID, syncInformationList.get(index).getUuid());

ActivityOptionsCompat options = ActivityOptionsCompat.makeClipRevealAnimation(v.findViewById(R.id.rootLayout), (int) v.getX(), (int) v.getY(), v.getWidth(), v.getHeight());
startActivity(i, options.toBundle());


app/src/main/java/lu/circl/mispbump/activities/StartUpActivity.java → app/src/main/java/lu/circl/mispbump/activities/LauncherActivity.java 파일 보기

@@ -12,14 +12,14 @@ import lu.circl.mispbump.auxiliary.PreferenceManager;
/**
* Starts either the login or home activity.
*/
public class StartUpActivity extends AppCompatActivity {
public class LauncherActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

if (isUserLoggedIn()) {
Intent home = new Intent(this, HomeActivity.class);
Intent home = new Intent(this, NetworkTestActivity.class);
startActivity(home);
} else {
Intent login = new Intent(this, LoginActivity.class);

+ 99
- 0
app/src/main/java/lu/circl/mispbump/activities/NetworkTestActivity.java 파일 보기

@@ -0,0 +1,99 @@
package lu.circl.mispbump.activities;


import android.os.Bundle;
import android.util.Log;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.util.Pair;

import java.util.List;

import lu.circl.mispbump.R;
import lu.circl.mispbump.auxiliary.MispRestClient;
import lu.circl.mispbump.auxiliary.PreferenceManager;
import lu.circl.mispbump.interfaces.MispService;
import lu.circl.mispbump.models.SyncInformation;
import lu.circl.mispbump.models.restModels.MispOrganisation;
import lu.circl.mispbump.models.restModels.MispServer;
import lu.circl.mispbump.models.restModels.Organisation;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;


public class NetworkTestActivity extends AppCompatActivity {

private PreferenceManager preferenceManager;
private MispService service;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_network_test);

preferenceManager = PreferenceManager.getInstance(NetworkTestActivity.this);
Pair<String, String> credentials = preferenceManager.getUserCredentials();
MispRestClient restClient = MispRestClient.getInstance(credentials.first, credentials.second);
service = restClient.getService();

loadAllSyncs();
}

private void boundSyncInfoToServer() {
List<SyncInformation> syncInformationList = preferenceManager.getSyncInformationList();

for (SyncInformation syncInfo : syncInformationList) {
String authkey = syncInfo.getSyncServer().getAuthkey();
String localUUID = syncInfo.getLocal().getOrganisation().getUuid();
String foreignUUID = syncInfo.getRemoteOrganisation().getUuid();


}
}

private void loadAllSyncs() {
Call<List<MispServer>> allServersCall = service.getAllServers();

allServersCall.enqueue(new Callback<List<MispServer>>() {
@Override
public void onResponse(Call<List<MispServer>> call, Response<List<MispServer>> response) {
if (!response.isSuccessful()) {
return;
}

List<MispServer> allServers = response.body();

assert allServers != null;

for (MispServer mispServer : allServers) {
loadOrganisation(mispServer.getRemoteOrganisation().getId());
}
}
@Override
public void onFailure(Call<List<MispServer>> call, Throwable t) {

}
});
}

private void loadOrganisation(int id) {
Call<MispOrganisation> organisationCall = service.getOrganisation(id);
organisationCall.enqueue(new Callback<MispOrganisation>() {
@Override
public void onResponse(Call<MispOrganisation> call, Response<MispOrganisation> response) {
if (!response.isSuccessful()) {
return;
}

Organisation org = response.body().organisation;
Log.d("DEBUG", org.toString());

}
@Override
public void onFailure(Call<MispOrganisation> call, Throwable t) {

}
});
}
}

+ 11
- 23
app/src/main/java/lu/circl/mispbump/activities/ProfileActivity.java 파일 보기

@@ -1,7 +1,6 @@
package lu.circl.mispbump.activities;


import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Shader;
import android.graphics.drawable.AnimatedVectorDrawable;
@@ -120,13 +119,10 @@ public class ProfileActivity extends AppCompatActivity {
}

private View.OnClickListener onFabClicked() {
return new View.OnClickListener() {
@Override
public void onClick(View v) {
fab.setImageDrawable(fabLoadingDrawable);
fabLoadingDrawable.start();
updateProfile();
}
return v -> {
fab.setImageDrawable(fabLoadingDrawable);
fabLoadingDrawable.start();
updateProfile();
};
}

@@ -181,23 +177,15 @@ public class ProfileActivity extends AppCompatActivity {

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.setNegativeButton("Discard", (dialog, which) -> dialog.cancel());

builder.setPositiveButton("Delete & Logout", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
preferenceManager.clearAllData();
KeyStoreWrapper.deleteAllStoredKeys();
builder.setPositiveButton("Delete & Logout", (dialog, which) -> {
preferenceManager.clearAllData();
KeyStoreWrapper.deleteAllStoredKeys();

Intent login = new Intent(getApplicationContext(), LoginActivity.class);
startActivity(login);
finish();
}
Intent login = new Intent(getApplicationContext(), LoginActivity.class);
startActivity(login);
finish();
});

builder.create().show();


+ 190
- 0
app/src/main/java/lu/circl/mispbump/activities/SyncInfoDetailActivity.java 파일 보기

@@ -0,0 +1,190 @@
package lu.circl.mispbump.activities;


import android.animation.ValueAnimator;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroupOverlay;
import android.widget.CheckBox;
import android.widget.LinearLayout;

import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;

import com.google.android.material.floatingactionbutton.FloatingActionButton;

import java.util.UUID;

import lu.circl.mispbump.R;
import lu.circl.mispbump.auxiliary.PreferenceManager;
import lu.circl.mispbump.customViews.MaterialPasswordView;
import lu.circl.mispbump.customViews.MaterialPreferenceText;
import lu.circl.mispbump.models.SyncInformation;


public class SyncInfoDetailActivity extends AppCompatActivity {

public static String EXTRA_SYNC_INFO_UUID = "EXTRA_SYNC_INFO_UUID";

private PreferenceManager preferenceManager;
private SyncInformation syncInformation;

private boolean fabMenuExpanded;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sync_info_detail);

preferenceManager = PreferenceManager.getInstance(SyncInfoDetailActivity.this);
syncInformation = preferenceManager.getSyncInformation(getExtraUuid());

if (syncInformation == null) {
throw new RuntimeException("Could not find UploadInformation with UUID {" + getExtraUuid().toString() + "}");
}

initToolbar();
initFabMenu();
populateContent();
}

@Override
protected void onPause() {
super.onPause();
preferenceManager.addSyncInformation(syncInformation);
}


private UUID getExtraUuid() {
return (UUID) getIntent().getSerializableExtra(EXTRA_SYNC_INFO_UUID);
}

private void initToolbar() {
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);

ActionBar ab = getSupportActionBar();
assert ab != null;

ab.setDisplayHomeAsUpEnabled(true);
}

private void initFabMenu() {
FloatingActionButton fab = findViewById(R.id.fab_main);
FloatingActionButton fabUpload = findViewById(R.id.fab_upload);
FloatingActionButton fabDownload = findViewById(R.id.fab_download);

LinearLayout uploadLayout = findViewById(R.id.layout_upload);
LinearLayout downloadLayout = findViewById(R.id.layout_download);

uploadLayout.setVisibility(View.GONE);
downloadLayout.setVisibility(View.GONE);

fab.setOnClickListener(view -> {
if (fabMenuExpanded) {
uploadLayout.setVisibility(View.GONE);
downloadLayout.setVisibility(View.GONE);

fabMenuExpanded = false;
} else {
uploadLayout.setVisibility(View.VISIBLE);
downloadLayout.setVisibility(View.VISIBLE);

fabMenuExpanded = true;
}
});

fabUpload.setOnClickListener(view -> {

preferenceManager.addSyncInformation(syncInformation);

Intent upload = new Intent(SyncInfoDetailActivity.this, UploadActivity.class);
upload.putExtra(UploadActivity.EXTRA_SYNC_INFO_UUID, syncInformation.getUuid().toString());
startActivity(upload);
});

fabDownload.setOnClickListener(view -> {

});
}

private void populateContent() {

// information

MaterialPreferenceText name = findViewById(R.id.name);
name.setSubtitle(syncInformation.getRemoteOrganisation().getName());

MaterialPreferenceText uuid = findViewById(R.id.uuid);
uuid.setSubtitle(syncInformation.getRemoteOrganisation().getUuid());

MaterialPreferenceText sector = findViewById(R.id.sector);
sector.setSubtitle(syncInformation.getRemoteOrganisation().getSector());

MaterialPreferenceText description = findViewById(R.id.description);
description.setSubtitle(syncInformation.getRemoteOrganisation().getDescription());

// settings

CheckBox allowSelfSigned = findViewById(R.id.checkbox_self_signed);
allowSelfSigned.setChecked(syncInformation.getSyncServer().getSelf_signed());
allowSelfSigned.setOnCheckedChangeListener((compoundButton, b) -> {
syncInformation.getSyncServer().setSelf_signed(b);

});

CheckBox push = findViewById(R.id.checkbox_push);
push.setChecked(syncInformation.getSyncServer().getPush());
push.setOnCheckedChangeListener((compoundButton, b) -> syncInformation.getSyncServer().setPush(b));

CheckBox pull = findViewById(R.id.checkbox_pull);
pull.setChecked(syncInformation.getSyncServer().getPull());
pull.setOnCheckedChangeListener((compundButton, b) -> syncInformation.getSyncServer().setPull(b));

CheckBox cache = findViewById(R.id.checkbox_cache);
cache.setChecked(syncInformation.getSyncServer().getCaching_enabled());
cache.setOnCheckedChangeListener((compoundButton, b) -> syncInformation.getSyncServer().setCaching_enabled(b));

// credentials

MaterialPreferenceText email = findViewById(R.id.email);
email.setSubtitle(syncInformation.getLocal().getSyncUser().getEmail());

MaterialPasswordView password = findViewById(R.id.password);
password.setPassword(syncInformation.getLocal().getSyncUser().getPassword());

MaterialPasswordView authkey = findViewById(R.id.authkey);
authkey.setPassword(syncInformation.getLocal().getSyncUser().getAuthkey());
}


public static void applyDim(@NonNull ViewGroup parent, float dimAmount) {
// ViewGroup root = (ViewGroup) getWindow().getDecorView().getRootView();
Drawable dim = new ColorDrawable(Color.BLACK);
dim.setBounds(0, 0, parent.getWidth(), parent.getHeight());

ValueAnimator valueAnimator = ValueAnimator.ofFloat(dimAmount);

valueAnimator.addUpdateListener(valueAnim -> {
float value = (float) valueAnim.getAnimatedValue();
dim.setAlpha((int) (255 * value));
ViewGroupOverlay overlay = parent.getOverlay();
overlay.add(dim);
});

valueAnimator.start();
}

public static void clearDim(@NonNull ViewGroup parent) {
ViewGroupOverlay overlay = parent.getOverlay();
overlay.clear();
}
}

+ 105
- 178
app/src/main/java/lu/circl/mispbump/activities/UploadActivity.java 파일 보기

@@ -6,21 +6,19 @@ import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;

import androidx.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.util.Pair;

import com.google.android.material.floatingactionbutton.FloatingActionButton;

import java.util.UUID;

import lu.circl.mispbump.R;
import lu.circl.mispbump.auxiliary.MispRestClient;
import lu.circl.mispbump.auxiliary.PreferenceManager;
import lu.circl.mispbump.customViews.UploadAction;
import lu.circl.mispbump.models.UploadInformation;
import lu.circl.mispbump.customViews.ProgressActionView;
import lu.circl.mispbump.models.SyncInformation;
import lu.circl.mispbump.models.restModels.Organisation;
import lu.circl.mispbump.models.restModels.Server;
import lu.circl.mispbump.models.restModels.User;
@@ -28,13 +26,74 @@ import lu.circl.mispbump.models.restModels.User;

public class UploadActivity extends AppCompatActivity {

public static String EXTRA_UPLOAD_INFO = "uploadInformation";
public static final String EXTRA_SYNC_INFO_UUID = "EXTRA_SYNC_INFO_UUID";

private View rootLayout;

private PreferenceManager preferenceManager;
private UploadInformation uploadInformation;
private MispRestClient mispRest;
private SyncInformation syncInformation;

private ProgressActionView availableAction, organisationAction, userAction, serverAction;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_upload);

rootLayout = findViewById(R.id.rootLayout);

preferenceManager = PreferenceManager.getInstance(UploadActivity.this);

Pair<String, String> credentials = preferenceManager.getUserCredentials();
mispRest = MispRestClient.getInstance(credentials.first, credentials.second);

parseExtra();
initToolbar();
initProgressActionViews();
startUpload();
}

@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
}

return false;
}


private void parseExtra() {
Intent i = getIntent();
String syncInfoUuid = i.getStringExtra(EXTRA_SYNC_INFO_UUID);
syncInformation = preferenceManager.getSyncInformation(UUID.fromString(syncInfoUuid));
}

private void initToolbar() {
Toolbar myToolbar = findViewById(R.id.toolbar);
setSupportActionBar(myToolbar);

ActionBar ab = getSupportActionBar();
if (ab != null) {
ab.setDisplayHomeAsUpEnabled(true);
ab.setDisplayShowTitleEnabled(true);
}
}

private void initProgressActionViews() {
availableAction = findViewById(R.id.availableProgressAction);
organisationAction = findViewById(R.id.organisationProgressAction);
userAction = findViewById(R.id.userProgressAction);
serverAction = findViewById(R.id.serverProgressAction);

availableAction.pending();
organisationAction.pending();
userAction.pending();
serverAction.pending();
}

private MispRestClient restClient;
private UploadAction availableAction, orgAction, userAction, serverAction;

private MispRestClient.AvailableCallback availableCallback = new MispRestClient.AvailableCallback() {
@Override
@@ -92,187 +151,64 @@ public class UploadActivity extends AppCompatActivity {
}
};

private FloatingActionButton fab;

private boolean errorWhileUpload;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_upload);

preferenceManager = PreferenceManager.getInstance(UploadActivity.this);
Pair<String, String> credentials = preferenceManager.getUserCredentials();
restClient = MispRestClient.getInstance(credentials.first, credentials.second);

parseExtra();
initViews();
startUpload();
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {

if (item.getItemId() == android.R.id.home) {
saveCurrentState();
finish();
return true;
}

return super.onOptionsItemSelected(item);
}

@Override
public void onBackPressed() {
super.onBackPressed();
saveCurrentState();
}


private void parseExtra() {
Intent i = getIntent();

UUID currentUUID = (UUID) i.getSerializableExtra(EXTRA_UPLOAD_INFO);

for (UploadInformation ui : preferenceManager.getUploadInformationList()) {
if (ui.getUuid().compareTo(currentUUID) == 0) {
uploadInformation = ui;
return;
}
}

if (uploadInformation == null) {
throw new RuntimeException("Could not find UploadInfo with UUID {" + currentUUID.toString() + "}");
}
}

private void initViews() {
getWindow().setStatusBarColor(getColor(R.color.colorPrimary));

fab = findViewById(R.id.fab);
fab.hide();

// toolbar
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar ab = getSupportActionBar();
assert ab != null;

ab.setDisplayShowTitleEnabled(false);
ab.setDisplayHomeAsUpEnabled(true);
ab.setHomeAsUpIndicator(R.drawable.ic_close);

availableAction = findViewById(R.id.availableAction);
orgAction = findViewById(R.id.orgAction);
userAction = findViewById(R.id.userAction);
serverAction = findViewById(R.id.serverAction);
}

private void saveCurrentState() {
if (errorWhileUpload) {
uploadInformation.setCurrentSyncStatus(UploadInformation.SyncStatus.FAILURE);
}
preferenceManager.addUploadInformation(uploadInformation);
}

private void setUploadActionState(UploadAction uploadAction, UploadAction.UploadState state, @Nullable String error) {
uploadAction.setCurrentUploadState(state);
uploadAction.setError(error);

switch (state) {
case PENDING:
if (fab.isShown()) {
fab.hide();
}
break;
case LOADING:
errorWhileUpload = false;
if (fab.isShown()) {
fab.hide();
}
break;
case DONE:
errorWhileUpload = false;
break;
case ERROR:
uploadInformation.setCurrentSyncStatus(UploadInformation.SyncStatus.FAILURE);

fab.setImageResource(R.drawable.ic_autorenew);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setUploadActionState(availableAction, UploadAction.UploadState.LOADING, null);
startUpload();
}
});
if (!fab.isShown()) {
fab.show();
}
errorWhileUpload = true;
break;
}
}


private User generateSyncUser(Organisation organisation) {
User syncUser = new User();
User syncUser = syncInformation.getSyncUser();

syncUser.setOrg_id(organisation.getId());
// syncUser.role_id = User.ROLE_SYNC_USER;
syncUser.setEmail(uploadInformation.getRemote().syncUserEmail);
syncUser.setPassword(uploadInformation.getRemote().syncUserPassword);
syncUser.setAuthkey(uploadInformation.getRemote().syncUserAuthkey);
syncUser.setRole_id(6);
syncUser.setTermsaccepted(true);

return syncUser;
}

private Server generateSyncServer() {
Server server = new Server();
server.setName(uploadInformation.getRemote().organisation.getName() + "'s Sync Server");
server.setUrl(uploadInformation.getRemote().baseUrl);
server.setRemote_org_id(uploadInformation.getRemote().organisation.getId());
server.setAuthkey(uploadInformation.getLocal().syncUserAuthkey);
server.setPull(uploadInformation.isPull());
server.setPush(uploadInformation.isPush());
server.setCaching_enabled(uploadInformation.isCached());
server.setSelf_signed(uploadInformation.isAllowSelfSigned());
Server server = syncInformation.getSyncServer();
server.setName(syncInformation.getRemoteOrganisation().getName() + "'s Sync Server");
server.setRemote_org_id(syncInformation.getRemoteOrganisation().getId());
server.setAuthkey(syncInformation.getLocal().getSyncUser().getAuthkey());
server.setPull(syncInformation.getSyncServer().getPull());
server.setPush(syncInformation.getSyncServer().getPush());
server.setCaching_enabled(syncInformation.getSyncServer().getCaching_enabled());
server.setSelf_signed(syncInformation.getSyncServer().getCaching_enabled());
return server;
}


/**
* Start upload to misp instance.
*/
private void startUpload() {
availableAction.setCurrentUploadState(UploadAction.UploadState.LOADING);
restClient.isAvailable(availableCallback);
availableAction.start();
mispRest.isAvailable(availableCallback);
}

private void mispAvailable(boolean available, String error) {
if (available) {
setUploadActionState(availableAction, UploadAction.UploadState.DONE, null);
restClient.addOrganisation(uploadInformation.getRemote().organisation, organisationCallback);
availableAction.done();
organisationAction.start();

mispRest.addOrganisation(syncInformation.getRemoteOrganisation(), organisationCallback);
} else {
setUploadActionState(availableAction, UploadAction.UploadState.ERROR, error);
availableAction.error(error);
}
}

private void organisationAdded(Organisation organisation) {
if (organisation != null) {
setUploadActionState(orgAction, UploadAction.UploadState.DONE, null);
uploadInformation.getRemote().organisation.setId(organisation.getId());
restClient.addUser(generateSyncUser(organisation), userCallback);
organisationAction.done();
userAction.start();

syncInformation.getRemoteOrganisation().setId(organisation.getId());
mispRest.addUser(generateSyncUser(organisation), userCallback);
} else {
// search by UUID because the error does not give the actual ID
restClient.getOrganisation(uploadInformation.getRemote().organisation.getUuid(), new MispRestClient.OrganisationCallback() {
mispRest.getOrganisation(syncInformation.getRemoteOrganisation().getUuid(), new MispRestClient.OrganisationCallback() {
@Override
public void success(Organisation organisation) {
organisationAdded(organisation);
organisationAction.done("Organisation already on MISP instance");
}

@Override
public void failure(String error) {
setUploadActionState(orgAction, UploadAction.UploadState.ERROR, error);
organisationAction.error(error);
}
});
}
@@ -280,18 +216,21 @@ public class UploadActivity extends AppCompatActivity {

private void userAdded(User user) {
if (user != null) {
setUploadActionState(userAction, UploadAction.UploadState.DONE, null);
restClient.getAllServers(allServersCallback);
userAction.done();
serverAction.start();

mispRest.getAllServers(allServersCallback);
} else {
restClient.getUser(uploadInformation.getRemote().syncUserEmail, new MispRestClient.UserCallback() {
mispRest.getUser(syncInformation.getLocal().getSyncUser().getEmail(), new MispRestClient.UserCallback() {
@Override
public void success(User user) {
userAction.done("User already on MISP instance");
userAdded(user);
}

@Override
public void failure(String error) {
setUploadActionState(userAction, UploadAction.UploadState.ERROR, error);
userAction.error(error);
}
});
}
@@ -309,28 +248,16 @@ public class UploadActivity extends AppCompatActivity {
}
}

restClient.addServer(serverToUpload, serverCallback);
mispRest.addServer(serverToUpload, serverCallback);
} else {
setUploadActionState(serverAction, UploadAction.UploadState.ERROR, "Could not retrieve server information");
serverAction.error("Unknown error while creating the Sync Server");
}
}

private void serverAdded(Server server) {
if (server != null) {
setUploadActionState(serverAction, UploadAction.UploadState.DONE, null);
uploadInformation.setCurrentSyncStatus(UploadInformation.SyncStatus.COMPLETE);
saveCurrentState();

fab.setImageResource(R.drawable.ic_check);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
fab.show();
} else {
setUploadActionState(serverAction, UploadAction.UploadState.ERROR, "Could not add server");
serverAction.done();
preferenceManager.addSyncInformation(syncInformation);
}
}
}

+ 0
- 269
app/src/main/java/lu/circl/mispbump/activities/UploadInfoActivity.java 파일 보기

@@ -1,269 +0,0 @@
package lu.circl.mispbump.activities;


import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager;

import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;

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.fragments.UploadCredentialsFragment;
import lu.circl.mispbump.fragments.UploadSettingsFragment;
import lu.circl.mispbump.models.UploadInformation;


public class UploadInfoActivity extends AppCompatActivity {

public static String EXTRA_UPLOAD_INFO_UUID = "uploadInformationUuid";

private PreferenceManager preferenceManager;
private UploadInformation uploadInformation;
private ViewPagerAdapter viewPagerAdapter;

private FloatingActionButton fab;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_upload_information);

preferenceManager = PreferenceManager.getInstance(UploadInfoActivity.this);

// tint statusBar
getWindow().setStatusBarColor(getColor(R.color.colorPrimary));
// getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);

parseExtra();
initToolbar();
initViewPager();
initViews();
}

@Override
protected void onResume() {
super.onResume();

// refresh current uploadInformation
if (uploadInformation != null) {
uploadInformation = preferenceManager.getUploadInformation(uploadInformation.getUuid());
initContent();
}
}

@Override
public void onBackPressed() {
super.onBackPressed();
saveCurrentSettings();
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_upload_info, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.delete:
DialogManager.deleteSyncInformationDialog(UploadInfoActivity.this, new DialogManager.IDialogFeedback() {
@Override
public void positive() {
preferenceManager.removeUploadInformation(uploadInformation.getUuid());
finish();
}

@Override
public void negative() {
}
});

return true;

case android.R.id.home:
saveCurrentSettings();
finish();
return true;

default:
return super.onOptionsItemSelected(item);
}
}


private void parseExtra() {
Intent i = getIntent();

UUID currentUUID = (UUID) i.getSerializableExtra(EXTRA_UPLOAD_INFO_UUID);
uploadInformation = preferenceManager.getUploadInformation(currentUUID);

if (uploadInformation == null) {
throw new RuntimeException("Could not find UploadInformation with UUID {" + currentUUID.toString() + "}");
}
}

private void initToolbar() {
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);

ActionBar ab = getSupportActionBar();
assert ab != null;

TextView toolbarTitle = findViewById(R.id.toolbarTitle);
toolbarTitle.setText(uploadInformation.getRemote().organisation.getName());

ab.setHomeAsUpIndicator(R.drawable.ic_close);

ab.setDisplayShowTitleEnabled(false);
ab.setDisplayHomeAsUpEnabled(true);
}

private void initViewPager() {
ViewPager viewPager = findViewById(R.id.viewPager);
TabLayout tabLayout = findViewById(R.id.tabLayout);

viewPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager(), uploadInformation);
viewPager.setAdapter(viewPagerAdapter);

viewPager.addOnPageChangeListener(onPageChangeListener());

tabLayout.setupWithViewPager(viewPager);
}

private void initViews() {
fab = findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// for the UploadActivity to have the latest settings of this UploadInfoObject
saveCurrentSettings();

Intent i = new Intent(UploadInfoActivity.this, UploadActivity.class);
i.putExtra(UploadActivity.EXTRA_UPLOAD_INFO, uploadInformation.getUuid());
startActivity(i);
}
});
}

private void initContent() {
switch (uploadInformation.getCurrentSyncStatus()) {
case COMPLETE:

break;

case FAILURE:

break;

case PENDING:

break;

default:

break;
}
}

private void saveCurrentSettings() {
uploadInformation.setAllowSelfSigned(viewPagerAdapter.uploadSettingsFragment.getAllowSelfSigned());
uploadInformation.setPull(viewPagerAdapter.uploadSettingsFragment.getPull());
uploadInformation.setPush(viewPagerAdapter.uploadSettingsFragment.getPush());
uploadInformation.setCached(viewPagerAdapter.uploadSettingsFragment.getCache());

preferenceManager.addUploadInformation(uploadInformation);
}


private ViewPager.OnPageChangeListener onPageChangeListener() {
return new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (position == 0) {
float scale = (1 - positionOffset);
fab.setScaleX(scale);
fab.setScaleY(scale);
}
}

@Override
public void onPageSelected(int position) {

}

@Override
public void onPageScrollStateChanged(int state) {

}
};
}


class ViewPagerAdapter extends FragmentPagerAdapter {

private UploadSettingsFragment uploadSettingsFragment;
private UploadCredentialsFragment uploadCredentialsFragment;

ViewPagerAdapter(@NonNull FragmentManager fm, UploadInformation uploadInformation) {
super(fm, ViewPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
uploadSettingsFragment = new UploadSettingsFragment(uploadInformation);
uploadCredentialsFragment = new UploadCredentialsFragment(uploadInformation);
}

@NonNull
@Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return uploadSettingsFragment;

case 1:
return uploadCredentialsFragment;

default:
uploadSettingsFragment = new UploadSettingsFragment();
return uploadSettingsFragment;
}
}

@Nullable
@Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return getString(R.string.settings);

case 1:
return getString(R.string.credentials);

default:
return "N/A";
}
}

@Override
public int getCount() {
return 2;
}
}
}

+ 99
- 0
app/src/main/java/lu/circl/mispbump/adapters/SyncInfoAdapter.java 파일 보기

@@ -0,0 +1,99 @@
package lu.circl.mispbump.adapters;


import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Locale;

import lu.circl.mispbump.R;
import lu.circl.mispbump.interfaces.OnRecyclerItemClickListener;
import lu.circl.mispbump.models.SyncInformation;


public class SyncInfoAdapter extends RecyclerView.Adapter<SyncInfoAdapter.ViewHolder> {

private List<SyncInformation> items;
private OnRecyclerItemClickListener<Integer> onRecyclerPositionClickListener;


@NonNull
@Override
public SyncInfoAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.row_upload_information, viewGroup, false);
return new SyncInfoAdapter.ViewHolder(v);
}

@Override
public void onBindViewHolder(@NonNull final SyncInfoAdapter.ViewHolder holder, final int position) {
final SyncInformation item = items.get(position);

SimpleDateFormat monthFormatter = new SimpleDateFormat("MMM", Locale.getDefault());
SimpleDateFormat dayFormatter = new SimpleDateFormat("dd", Locale.getDefault());

holder.dateMonth.setText(monthFormatter.format(item.getSyncDate()));
holder.dateDay.setText(dayFormatter.format(item.getSyncDate()));

holder.orgName.setText(item.getRemoteOrganisation().getName());

// switch (item.getCurrentSyncStatus()) {
// case COMPLETE:
// ImageViewCompat.setImageTintList(holder.syncStatus, ColorStateList.valueOf(context.getColor(R.color.status_green)));
// holder.syncStatus.setImageResource(R.drawable.ic_check_outline);
// break;
// case FAILURE:
// ImageViewCompat.setImageTintList(holder.syncStatus, ColorStateList.valueOf(context.getColor(R.color.status_red)));
// holder.syncStatus.setImageResource(R.drawable.ic_error_outline);
// break;
// case PENDING:
// ImageViewCompat.setImageTintList(holder.syncStatus, ColorStateList.valueOf(context.getColor(R.color.status_amber)));
// holder.syncStatus.setImageResource(R.drawable.ic_pending);
// break;
// }

holder.rootView.setOnClickListener(view -> onRecyclerPositionClickListener.onClick(view, position));
}

@Override
public int getItemCount() {
return items.size();
}


public void setItems(List<SyncInformation> items) {
this.items = items;
notifyDataSetChanged();
}

public void setOnRecyclerPositionClickListener(OnRecyclerItemClickListener<Integer> onRecyclerPositionClickListener) {
this.onRecyclerPositionClickListener = onRecyclerPositionClickListener;
}


static class ViewHolder extends RecyclerView.ViewHolder {
View rootView;
ImageView syncStatus;
TextView orgName, dateMonth, dateDay;

ViewHolder(@NonNull View itemView) {
super(itemView);

rootView = itemView;

orgName = itemView.findViewById(R.id.orgName);

dateMonth = itemView.findViewById(R.id.date_month);
dateDay = itemView.findViewById(R.id.date_day);

syncStatus = itemView.findViewById(R.id.syncStatus);
}
}
}

+ 0
- 98
app/src/main/java/lu/circl/mispbump/adapters/UploadInfoAdapter.java 파일 보기

@@ -1,98 +0,0 @@
package lu.circl.mispbump.adapters;


import android.content.Context;
import android.content.res.ColorStateList;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.core.widget.ImageViewCompat;
import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

import lu.circl.mispbump.R;
import lu.circl.mispbump.interfaces.OnRecyclerItemClickListener;
import lu.circl.mispbump.models.UploadInformation;


public class UploadInfoAdapter extends RecyclerView.Adapter<UploadInfoAdapter.ViewHolder> {

private Context context;
private List<UploadInformation> items;

private OnRecyclerItemClickListener<Integer> onRecyclerPositionClickListener;


public UploadInfoAdapter(Context context) {
this.context = context;
}


@NonNull
@Override
public UploadInfoAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.row_upload_information, viewGroup, false);
return new UploadInfoAdapter.ViewHolder(v);
}

@Override
public void onBindViewHolder(@NonNull final UploadInfoAdapter.ViewHolder holder, final int position) {

final UploadInformation item = items.get(position);

holder.date.setText(item.getDateString());
holder.orgName.setText(item.getRemote().organisation.getName());

switch (item.getCurrentSyncStatus()) {
case COMPLETE:
ImageViewCompat.setImageTintList(holder.syncStatus, ColorStateList.valueOf(context.getColor(R.color.status_green)));
holder.syncStatus.setImageResource(R.drawable.ic_check_outline);
break;
case FAILURE:
ImageViewCompat.setImageTintList(holder.syncStatus, ColorStateList.valueOf(context.getColor(R.color.status_red)));
holder.syncStatus.setImageResource(R.drawable.ic_error_outline);
break;
case PENDING:
ImageViewCompat.setImageTintList(holder.syncStatus, ColorStateList.valueOf(context.getColor(R.color.status_amber)));
holder.syncStatus.setImageResource(R.drawable.ic_pending);
break;
}

holder.rootView.setOnClickListener(view -> onRecyclerPositionClickListener.onClick(view, position));
}

@Override
public int getItemCount() {
return items.size();
}


public void setItems(List<UploadInformation> items) {
this.items = items;
notifyDataSetChanged();
}

public void setOnRecyclerPositionClickListener(OnRecyclerItemClickListener<Integer> onRecyclerPositionClickListener) {
this.onRecyclerPositionClickListener = onRecyclerPositionClickListener;
}


static class ViewHolder extends RecyclerView.ViewHolder {
View rootView;
ImageView syncStatus;
TextView orgName, date;

ViewHolder(@NonNull View itemView) {
super(itemView);
rootView = itemView;
orgName = itemView.findViewById(R.id.orgName);
date = itemView.findViewById(R.id.date);
syncStatus = itemView.findViewById(R.id.syncStatus);
}
}
}

+ 6
- 6
app/src/main/java/lu/circl/mispbump/auxiliary/DialogManager.java 파일 보기

@@ -27,11 +27,11 @@ public class DialogManager {
// this dialog needs definite user feedback
adb.setCancelable(false);

if (oldSync.organisation.getName().equals(newSync.organisation.getName())) {
adb.setTitle("Already Synced with " + oldSync.organisation.getName());
} else {
adb.setTitle("Already Synced with " + oldSync.organisation.getName() + "(Now:" + newSync.organisation.getName() + ")");
}
// if (oldSync.organisation.getName().equals(newSync.organisation.getName())) {
// adb.setTitle("Already Synced with " + oldSync.organisation.getName());
// } else {
// adb.setTitle("Already Synced with " + oldSync.organisation.getName() + "(Now:" + newSync.organisation.getName() + ")");
// }

adb.setMessage("");

@@ -143,7 +143,7 @@ public class DialogManager {
final AlertDialog.Builder adb = new AlertDialog.Builder(context);

adb.setTitle("Sync information received");
adb.setMessage(syncInformation.organisation.getName());
adb.setMessage(syncInformation.getRemoteOrganisation().getName());
adb.setPositiveButton("Accept", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {


+ 20
- 15
app/src/main/java/lu/circl/mispbump/auxiliary/MispRestClient.java 파일 보기

@@ -18,7 +18,7 @@ import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import lu.circl.mispbump.interfaces.MispRestInterface;
import lu.circl.mispbump.interfaces.MispService;
import lu.circl.mispbump.models.restModels.MispOrganisation;
import lu.circl.mispbump.models.restModels.MispRole;
import lu.circl.mispbump.models.restModels.MispServer;
@@ -47,7 +47,7 @@ public class MispRestClient {


private static MispRestClient instance;
private MispRestInterface mispRestInterface;
private MispService mispService;

public static MispRestClient getInstance(String url, String authkey) {
if (instance == null) {
@@ -67,13 +67,18 @@ public class MispRestClient {
.client(getCustomClient(true, true, authkey))
.build();

mispRestInterface = retrofit.create(MispRestInterface.class);
mispService = retrofit.create(MispService.class);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
}
}


public MispService getService() {
return mispService;
}


/**
* @param unsafe whether to accept all certificates or only trusted ones
* @param logging whether to log Retrofit calls (for debugging)
@@ -153,7 +158,7 @@ public class MispRestClient {
* @param callback {@link AvailableCallback}
*/
public void isAvailable(final AvailableCallback callback) {
Call<Version> call = mispRestInterface.pyMispVersion();
Call<Version> call = mispService.pyMispVersion();
call.enqueue(new Callback<Version>() {
@Override
public void onResponse(@NonNull Call<Version> call, @NonNull Response<Version> response) {
@@ -177,7 +182,7 @@ public class MispRestClient {
}

public void getRoles(final AllRolesCallback callback) {
Call<List<MispRole>> call = mispRestInterface.getRoles();
Call<List<MispRole>> call = mispService.getRoles();
call.enqueue(new Callback<List<MispRole>>() {
@Override
public void onResponse(Call<List<MispRole>> call, Response<List<MispRole>> response) {
@@ -213,7 +218,7 @@ public class MispRestClient {
* @param callback {@link UserCallback} wrapper to return user directly
*/
public void getMyUser(final UserCallback callback) {
Call<MispUser> call = mispRestInterface.getMyUserInformation();
Call<MispUser> call = mispService.getMyUserInformation();

call.enqueue(new Callback<MispUser>() {
@Override
@@ -245,7 +250,7 @@ public class MispRestClient {
*/

public void getUser(int userId, final UserCallback callback) {
Call<MispUser> call = mispRestInterface.getUser(userId);
Call<MispUser> call = mispService.getUser(userId);

call.enqueue(new Callback<MispUser>() {
@Override
@@ -291,7 +296,7 @@ public class MispRestClient {
}

public void getAllUsers(final AllUsersCallback callback) {
Call<List<MispUser>> call = mispRestInterface.getAllUsers();
Call<List<MispUser>> call = mispService.getAllUsers();

call.enqueue(new Callback<List<MispUser>>() {
@Override
@@ -327,7 +332,7 @@ public class MispRestClient {
* @param callback {@link UserCallback} wrapper to return the created user directly
*/
public void addUser(User user, final UserCallback callback) {
Call<MispUser> call = mispRestInterface.addUser(user);
Call<MispUser> call = mispService.addUser(user);

call.enqueue(new Callback<MispUser>() {
@Override
@@ -357,7 +362,7 @@ public class MispRestClient {
* @param callback {@link OrganisationCallback} wrapper to return a organisation directly
*/
public void getOrganisation(int orgId, final OrganisationCallback callback) {
Call<MispOrganisation> call = mispRestInterface.getOrganisation(orgId);
Call<MispOrganisation> call = mispService.getOrganisation(orgId);

call.enqueue(new Callback<MispOrganisation>() {
@Override
@@ -402,7 +407,7 @@ public class MispRestClient {
}

public void getAllOrganisations(final AllOrganisationsCallback callback) {
Call<List<MispOrganisation>> call = mispRestInterface.getAllOrganisations();
Call<List<MispOrganisation>> call = mispService.getAllOrganisations();

call.enqueue(new Callback<List<MispOrganisation>>() {
@Override
@@ -438,7 +443,7 @@ public class MispRestClient {
* @param callback {@link OrganisationCallback} wrapper to return the created organisation directly
*/
public void addOrganisation(Organisation organisation, final OrganisationCallback callback) {
Call<MispOrganisation> call = mispRestInterface.addOrganisation(organisation);
Call<MispOrganisation> call = mispService.addOrganisation(organisation);

call.enqueue(new Callback<MispOrganisation>() {
@Override
@@ -466,7 +471,7 @@ public class MispRestClient {
* @param callback {@link OrganisationCallback} wrapper to return a list of servers directly
*/
public void getAllServers(final AllServersCallback callback) {
Call<List<MispServer>> call = mispRestInterface.getAllServers();
Call<List<MispServer>> call = mispService.getAllServers();

call.enqueue(new Callback<List<MispServer>>() {
@Override
@@ -494,7 +499,7 @@ public class MispRestClient {
}

public void getAllServers(final AllRawServersCallback callback) {
Call<List<MispServer>> call = mispRestInterface.getAllServers();
Call<List<MispServer>> call = mispService.getAllServers();

call.enqueue(new Callback<List<MispServer>>() {
@Override
@@ -520,7 +525,7 @@ public class MispRestClient {
* @param callback {@link ServerCallback} wrapper to return the created server directly
*/
public void addServer(Server server, final ServerCallback callback) {
Call<Server> call = mispRestInterface.addServer(server);
Call<Server> call = mispService.addServer(server);

call.enqueue(new Callback<Server>() {
@Override


+ 48
- 70
app/src/main/java/lu/circl/mispbump/auxiliary/PreferenceManager.java 파일 보기

@@ -21,7 +21,7 @@ import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import lu.circl.mispbump.models.UploadInformation;
import lu.circl.mispbump.models.SyncInformation;
import lu.circl.mispbump.models.restModels.Organisation;
import lu.circl.mispbump.models.restModels.Role;
import lu.circl.mispbump.models.restModels.User;
@@ -36,7 +36,7 @@ public class PreferenceManager {
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 static final String SYNC_INFO = "sync_info";

private static final String MISP_ROLES = "misp_roles";

@@ -101,21 +101,11 @@ public class PreferenceManager {
*/
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);
KeyStoreWrapper keyStoreWrapper = new KeyStoreWrapper(KeyStoreWrapper.USER_INFO_ALIAS);
editor.putString(USER_INFOS, keyStoreWrapper.encrypt(new Gson().toJson(user)));
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) {
} catch (BadPaddingException | InvalidKeyException | NoSuchAlgorithmException | IllegalBlockSizeException | NoSuchPaddingException e) {
e.printStackTrace();
}
}
@@ -135,17 +125,7 @@ public class PreferenceManager {
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) {
} catch (BadPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchAlgorithmException | IllegalBlockSizeException | NoSuchPaddingException e) {
e.printStackTrace();
}

@@ -243,58 +223,57 @@ public class PreferenceManager {
}


private List<UploadInformation> cachedUploadInformationList;
private List<SyncInformation> cachedSyncInformationList;

private void loadUploadInformationList() {
KeyStoreWrapper ksw = new KeyStoreWrapper(KeyStoreWrapper.UPLOAD_INFORMATION_ALIAS);
String storedUploadInfoString = preferences.getString(UPLOAD_INFO, null);
private void loadSyncInformationList() {
KeyStoreWrapper ksw = new KeyStoreWrapper(KeyStoreWrapper.SYNC_INFORMATION_ALIAS);
String storedSyncInfoString = preferences.getString(SYNC_INFO, null);

Type type = new TypeToken<List<UploadInformation>>() {
}.getType();
Type type = new TypeToken<List<SyncInformation>>() {}.getType();

if (storedUploadInfoString == null || storedUploadInfoString.isEmpty()) {
cachedUploadInformationList = new ArrayList<>();
if (storedSyncInfoString == null || storedSyncInfoString.isEmpty()) {
cachedSyncInformationList = new ArrayList<>();
} else {
try {
storedUploadInfoString = ksw.decrypt(storedUploadInfoString);
cachedUploadInformationList = new Gson().fromJson(storedUploadInfoString, type);
storedSyncInfoString = ksw.decrypt(storedSyncInfoString);
cachedSyncInformationList = new Gson().fromJson(storedSyncInfoString, type);
} catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException | NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
}

private void saveUploadInformationList() {
private void saveSyncInformationList() {
try {
KeyStoreWrapper ksw = new KeyStoreWrapper(KeyStoreWrapper.UPLOAD_INFORMATION_ALIAS);
String cipherText = ksw.encrypt(new Gson().toJson(cachedUploadInformationList));
KeyStoreWrapper ksw = new KeyStoreWrapper(KeyStoreWrapper.SYNC_INFORMATION_ALIAS);
String cipherText = ksw.encrypt(new Gson().toJson(cachedSyncInformationList));
SharedPreferences.Editor editor = preferences.edit();
editor.putString(UPLOAD_INFO, cipherText);
editor.putString(SYNC_INFO, cipherText);
editor.apply();
} catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException e) {
e.printStackTrace();
}
}

public List<UploadInformation> getUploadInformationList() {
if (cachedUploadInformationList == null) {
loadUploadInformationList();
public List<SyncInformation> getSyncInformationList() {
if (cachedSyncInformationList == null) {
loadSyncInformationList();
}

return cachedUploadInformationList;
return cachedSyncInformationList;
}

public void setUploadInformationList(List<UploadInformation> uploadInformationList) {
cachedUploadInformationList = uploadInformationList;
saveUploadInformationList();
public void setSyncInformationList(List<SyncInformation> uploadInformationList) {
cachedSyncInformationList = uploadInformationList;
saveSyncInformationList();
}

public UploadInformation getUploadInformation(UUID uuid) {
if (cachedUploadInformationList == null) {
loadUploadInformationList();
public SyncInformation getSyncInformation(UUID uuid) {
if (cachedSyncInformationList == null) {
loadSyncInformationList();
}

for (UploadInformation ui : cachedUploadInformationList) {
for (SyncInformation ui : cachedSyncInformationList) {
if (ui.getUuid().compareTo(uuid) == 0) {
return ui;
}
@@ -303,51 +282,50 @@ public class PreferenceManager {
return null;
}

public void addUploadInformation(UploadInformation uploadInformation) {
if (cachedUploadInformationList == null) {
loadUploadInformationList();
public void addSyncInformation(SyncInformation syncInformation) {
if (cachedSyncInformationList == null) {
loadSyncInformationList();
}

// update if exists
for (int i = 0; i < cachedUploadInformationList.size(); i++) {
if (cachedUploadInformationList.get(i).getUuid().compareTo(uploadInformation.getUuid()) == 0) {
cachedUploadInformationList.set(i, uploadInformation);
saveUploadInformationList();
for (int i = 0; i < cachedSyncInformationList.size(); i++) {
if (cachedSyncInformationList.get(i).getUuid().compareTo(syncInformation.getUuid()) == 0) {
cachedSyncInformationList.set(i, syncInformation);
saveSyncInformationList();
return;
}
}

// else: add
cachedUploadInformationList.add(uploadInformation);
saveUploadInformationList();
cachedSyncInformationList.add(syncInformation);
saveSyncInformationList();
}

public void removeUploadInformation(UUID uuid) {
if (cachedUploadInformationList == null) {
loadUploadInformationList();
if (cachedSyncInformationList == null) {
loadSyncInformationList();
}

for (UploadInformation ui : cachedUploadInformationList) {
for (SyncInformation ui : cachedSyncInformationList) {
if (ui.getUuid().compareTo(uuid) == 0) {
// if is last element, then clear everything including IV and key in KeyStore
if (cachedUploadInformationList.size() == 1) {
if (cachedSyncInformationList.size() == 1) {
clearUploadInformation();
} else {
cachedUploadInformationList.remove(ui);
saveUploadInformationList();
cachedSyncInformationList.remove(ui);
saveSyncInformationList();
}
}
}
}

public void clearUploadInformation() {
cachedUploadInformationList.clear();
cachedSyncInformationList.clear();

KeyStoreWrapper keyStoreWrapper = new KeyStoreWrapper(KeyStoreWrapper.UPLOAD_INFORMATION_ALIAS);
KeyStoreWrapper keyStoreWrapper = new KeyStoreWrapper(KeyStoreWrapper.SYNC_INFORMATION_ALIAS);
keyStoreWrapper.deleteStoredKey();

SharedPreferences.Editor editor = preferences.edit();
editor.remove(UPLOAD_INFO);
editor.remove(SYNC_INFO);
editor.apply();
}



+ 5
- 26
app/src/main/java/lu/circl/mispbump/customViews/MaterialPasswordView.java 파일 보기

@@ -18,7 +18,6 @@ import lu.circl.mispbump.R;
public class MaterialPasswordView extends ConstraintLayout {

private TextView titleView, passwordView;
private OnCopyClickListener onCopyClickListener;


public MaterialPasswordView(Context context, AttributeSet attrs) {
@@ -31,14 +30,6 @@ public class MaterialPasswordView extends ConstraintLayout {
final String password = a.getString(R.styleable.MaterialPasswordView_password);
a.recycle();

ImageButton copyButton = view.findViewById(R.id.copy);
copyButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onCopyClickListener.onClick(title, getPassword());
}
});

titleView = view.findViewById(R.id.material_password_title);
titleView.setText(title);

@@ -47,14 +38,11 @@ public class MaterialPasswordView extends ConstraintLayout {
passwordView.setText(password);

ImageButton visibleToggle = findViewById(R.id.visibleToggle);
visibleToggle.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (passwordView.getTransformationMethod() == null) {
passwordView.setTransformationMethod(new PasswordTransformationMethod());
} else {
passwordView.setTransformationMethod(null);
}
visibleToggle.setOnClickListener(v -> {
if (passwordView.getTransformationMethod() == null) {
passwordView.setTransformationMethod(new PasswordTransformationMethod());
} else {
passwordView.setTransformationMethod(null);
}
});
}
@@ -84,13 +72,4 @@ public class MaterialPasswordView extends ConstraintLayout {
}
}


public void addOnCopyClickedListener(OnCopyClickListener listener) {
onCopyClickListener = listener;
}


public interface OnCopyClickListener {
void onClick(String title, String password);
}
}

+ 146
- 0
app/src/main/java/lu/circl/mispbump/customViews/ProgressActionView.java 파일 보기

@@ -0,0 +1,146 @@
package lu.circl.mispbump.customViews;


import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;

import androidx.annotation.Nullable;

import lu.circl.mispbump.R;


public class ProgressActionView extends LinearLayout {

private Context context;

private ImageView icon;
private ProgressBar progressBar;
private TextView title, feedback;

private Drawable pendingIcon, doneIcon, errorIcon;

public ProgressActionView(Context context) {
this(context, null);
}
public ProgressActionView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public ProgressActionView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public ProgressActionView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
this.context = context;
initViews(attrs);
}


private void initViews(AttributeSet attrs) {
View v = LayoutInflater.from(context).inflate(R.layout.view_upload_action, this, true);

icon = v.findViewById(R.id.progressIcon);
progressBar = v.findViewById(R.id.progressBar);
title = v.findViewById(R.id.title);
feedback = v.findViewById(R.id.error);

TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ProgressActionView);
title.setText(a.getString(R.styleable.ProgressActionView_action_title));
pendingIcon = context.getDrawable(a.getResourceId(R.styleable.ProgressActionView_action_pending_icon, 0));
doneIcon = context.getDrawable(a.getResourceId(R.styleable.ProgressActionView_action_done_icon, 0));
errorIcon = context.getDrawable(a.getResourceId(R.styleable.ProgressActionView_action_error_icon, 0));
a.recycle();

pendingIcon.setTint(context.getColor(R.color.status_amber));
doneIcon.setTint(context.getColor(R.color.status_green));
errorIcon.setTint(context.getColor(R.color.status_red));

pending();
icon.setImageTintList(ColorStateList.valueOf(context.getColor(R.color.status_amber)));
}


public void pending() {
progressBar.setVisibility(GONE);
switchIcon(pendingIcon, R.color.status_amber);
icon.setVisibility(VISIBLE);
}

public void start() {
progressBar.setVisibility(VISIBLE);

icon.setVisibility(GONE);
feedback.setVisibility(GONE);
}

public void done() {
done("");
}

public void done(String message) {
progressBar.setVisibility(GONE);

switchIcon(doneIcon, R.color.status_green);
icon.setVisibility(VISIBLE);

if (message.isEmpty()) {
feedback.setVisibility(GONE);
} else {
feedback.setTextColor(context.getColor(R.color.status_amber));
feedback.setText(message);
feedback.setVisibility(VISIBLE);
}
}

public void error(String error) {
progressBar.setVisibility(GONE);

switchIcon(errorIcon, R.color.status_red);
icon.setVisibility(VISIBLE);

feedback.setTextColor(context.getColor(R.color.status_red));
feedback.setText(error);
feedback.setVisibility(VISIBLE);
}

public void info(String info) {
progressBar.setVisibility(GONE);

switchIcon(errorIcon, R.color.status_amber);
icon.setVisibility(VISIBLE);

this.feedback.setTextColor(context.getColor(R.color.status_amber));
this.feedback.setText(info);
this.feedback.setVisibility(VISIBLE);
}


private void switchIcon(Drawable d, int color) {
icon.setImageDrawable(d);
icon.setImageTintList(ColorStateList.valueOf(context.getColor(color)));
}

public void setTitle(String title) {
this.title.setText(title);
}

public void setErrorIconDrawable(Drawable d) {
errorIcon = d;
}

public void setPendingIconDrawable(Drawable d) {
pendingIcon = d;
}

public void setDoneIconDrawable(Drawable d) {
doneIcon = d;
}
}

+ 6
- 7
app/src/main/java/lu/circl/mispbump/customViews/UploadAction.java 파일 보기

@@ -3,7 +3,6 @@ package lu.circl.mispbump.customViews;

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;
@@ -45,15 +44,15 @@ public class UploadAction extends ConstraintLayout {

View baseView = LayoutInflater.from(context).inflate(R.layout.view_upload_action, this);

TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.UploadAction);
// TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.UploadAction);
//
// titleView = baseView.findViewById(R.id.title);
// titleView.setText(a.getString(R.styleable.UploadAction_description));

titleView = baseView.findViewById(R.id.title);
titleView.setText(a.getString(R.styleable.UploadAction_description));

a.recycle();
// a.recycle();

errorView = baseView.findViewById(R.id.error);
stateView = baseView.findViewById(R.id.stateView);
stateView = baseView.findViewById(R.id.progressIcon);
progressBar = baseView.findViewById(R.id.progressBar);

setCurrentUploadState(UploadState.PENDING);


+ 0
- 68
app/src/main/java/lu/circl/mispbump/fragments/UploadCredentialsFragment.java 파일 보기

@@ -1,68 +0,0 @@
package lu.circl.mispbump.fragments;


import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.fragment.app.Fragment;

import com.google.android.material.snackbar.Snackbar;

import lu.circl.mispbump.R;
import lu.circl.mispbump.customViews.MaterialPasswordView;
import lu.circl.mispbump.customViews.MaterialPreferenceText;
import lu.circl.mispbump.models.UploadInformation;


public class UploadCredentialsFragment extends Fragment {

private View rootLayout;
private UploadInformation uploadInformation;

public UploadCredentialsFragment() {
}

public UploadCredentialsFragment(UploadInformation uploadInformation) {
this.uploadInformation = uploadInformation;
}

private MaterialPasswordView.OnCopyClickListener onCopyClickListener = new MaterialPasswordView.OnCopyClickListener() {
@Override
public void onClick(String title, String password) {
ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText(title, password);
clipboard.setPrimaryClip(clip);
Snackbar.make(rootLayout, "Copied to clipboard", Snackbar.LENGTH_LONG).show();
}
};


@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_upload_credentials, container, false);

rootLayout = v.findViewById(R.id.rootLayout);

MaterialPreferenceText baseUrl = v.findViewById(R.id.baseUrl);
baseUrl.setSubtitle(uploadInformation.getRemote().baseUrl);

MaterialPreferenceText email = v.findViewById(R.id.email);
email.setSubtitle(uploadInformation.getLocal().syncUserEmail);

MaterialPasswordView authkey = v.findViewById(R.id.authkey);
authkey.setPassword(uploadInformation.getRemote().syncUserAuthkey);
authkey.addOnCopyClickedListener(onCopyClickListener);

MaterialPasswordView password = v.findViewById(R.id.password);
password.setPassword(uploadInformation.getRemote().syncUserPassword);
password.addOnCopyClickedListener(onCopyClickListener);

return v;
}

}

+ 0
- 89
app/src/main/java/lu/circl/mispbump/fragments/UploadSettingsFragment.java 파일 보기

@@ -1,89 +0,0 @@
package lu.circl.mispbump.fragments;


import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;

import lu.circl.mispbump.R;
import lu.circl.mispbump.customViews.MaterialPreferenceSwitch;
import lu.circl.mispbump.models.UploadInformation;


public class UploadSettingsFragment extends Fragment {

private MaterialPreferenceSwitch allowSelfSigned, push, pull, cache;
private UploadInformation uploadInformation;

public UploadSettingsFragment() {
}

public UploadSettingsFragment(UploadInformation uploadInformation) {
this.uploadInformation = uploadInformation;
}

@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_upload_settings, 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);

populateContent();

return v;
}

private void populateContent() {
if (uploadInformation == null) {
return;
}

allowSelfSigned.setChecked(uploadInformation.isAllowSelfSigned());
push.setChecked(uploadInformation.isPush());
pull.setChecked(uploadInformation.isPull());
cache.setChecked(uploadInformation.isCached());
}

public void setUploadInfo(UploadInformation uploadInfo) {
this.uploadInformation = uploadInfo;
}

public boolean getAllowSelfSigned() {
return allowSelfSigned.isChecked();
}

public void setAllowSelfSigned(boolean allowSelfSigned) {
this.allowSelfSigned.setChecked(allowSelfSigned);
}

public boolean getPush() {
return push.isChecked();
}

public void setPush(boolean push) {
this.push.setChecked(push);
}

public boolean getPull() {
return pull.isChecked();
}

public void setPull(boolean pull) {
this.pull.setChecked(pull);
}

public boolean getCache() {
return cache.isChecked();
}

public void setCache(boolean cache) {
this.cache.setChecked(cache);
}
}

app/src/main/java/lu/circl/mispbump/interfaces/MispRestInterface.java → app/src/main/java/lu/circl/mispbump/interfaces/MispService.java 파일 보기

@@ -21,7 +21,7 @@ import retrofit2.http.Path;
/**
* RetroFit2 interface for communication with misp instances
*/
public interface MispRestInterface {
public interface MispService {

// settings routes


+ 44
- 0
app/src/main/java/lu/circl/mispbump/models/ExchangeInformation.java 파일 보기

@@ -0,0 +1,44 @@
package lu.circl.mispbump.models;


import androidx.annotation.NonNull;

import lu.circl.mispbump.models.restModels.Organisation;
import lu.circl.mispbump.models.restModels.Server;
import lu.circl.mispbump.models.restModels.User;


public class ExchangeInformation {

private Organisation organisation;
private User syncUser;
private Server server;

public Organisation getOrganisation() {
return organisation;
}
public void setOrganisation(Organisation organisation) {
this.organisation = organisation;
}

public User getSyncUser() {
return syncUser;
}
public void setSyncUser(User syncUser) {
this.syncUser = syncUser;
}

public Server getServer() {
return server;
}
public void setServer(Server server) {
this.server = server;
}


@NonNull
@Override
public String toString() {
return "Exchange Information: \n" + organisation.toString() + "\n" + syncUser.toString() + "\n" + server.toString();
}
}

+ 102
- 13
app/src/main/java/lu/circl/mispbump/models/SyncInformation.java 파일 보기

@@ -3,29 +3,118 @@ package lu.circl.mispbump.models;

import androidx.annotation.NonNull;

import com.google.gson.annotations.SerializedName;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.UUID;

import lu.circl.mispbump.models.restModels.Organisation;
import lu.circl.mispbump.models.restModels.Server;
import lu.circl.mispbump.models.restModels.User;


/**
* A Class that holds the information needed synchronize two misp instances.
* Class that holds the information needed synchronize two misp instances.
*/
public class SyncInformation {

public Organisation organisation;
public String syncUserEmail;
public String syncUserPassword;
public String syncUserAuthkey;
public String baseUrl;
private UUID uuid;
private Date date, lastModified;

@SerializedName("organisation")
private Organisation remoteOrganisation;
private User syncUser;
private Server syncServer;
private ExchangeInformation remote;
private ExchangeInformation local;


public SyncInformation() {
uuid = UUID.randomUUID();
setSyncDate();
}


public UUID getUuid() {
return uuid;
}
public void setUuid(UUID uuid) {
this.uuid = uuid;
}

public Date getLastModified() {
return lastModified;
}
public void setLastModified() {
setLastModified(Calendar.getInstance().getTime());
}
public void setLastModified(Date date) {
lastModified = date;
}

public void setSyncDate() {
setSyncDate(Calendar.getInstance().getTime());
}
public void setSyncDate(Date date) {
this.date = date;
this.lastModified = date;
}
public Date getSyncDate() {
return date;
}
private String getSyncDateString() {
SimpleDateFormat df = new SimpleDateFormat("dd.MM.yyyy", Locale.getDefault());
return df.format(date);
}

public Organisation getRemoteOrganisation() {
return remoteOrganisation;
}
public void setRemoteOrganisation(Organisation organisation) {
this.remoteOrganisation = organisation;
}

public User getSyncUser() {
return syncUser;
}
public void setSyncUser(User syncUser) {
this.syncUser = syncUser;
}

public Server getSyncServer() {
return syncServer;
}
public void setSyncServer(Server syncServer) {
this.syncServer = syncServer;
}

public ExchangeInformation getLocal() {
return local;
}
public void setLocal(ExchangeInformation local) {
this.local = local;
}


public void populateRemoteExchangeInformation(ExchangeInformation exchangeInformation) {
this.remoteOrganisation = exchangeInformation.getOrganisation();
this.syncUser = exchangeInformation.getSyncUser();
this.syncServer = exchangeInformation.getServer();
}


@NonNull
@Override
public String toString() {
return "SyncInformation{" +
"organisation=" + organisation +
", syncUserEmail='" + syncUserEmail + '\'' +
", syncUserPassword='" + syncUserPassword + '\'' +
", syncUserAuthkey='" + syncUserAuthkey + '\'' +
", baseUrl='" + baseUrl + '\'' +
'}';
return "Sync Information: \n" +
"UUID = " + uuid + "\n" +
"Date = " + getSyncDateString() + "\n" +
remoteOrganisation.toString() + "\n" +
syncUser.toString() + "\n" +
syncServer.toString() + "\n" +
local.toString();
}
}

+ 0
- 91
app/src/main/java/lu/circl/mispbump/models/SyncModel.java 파일 보기

@@ -1,91 +0,0 @@
package lu.circl.mispbump.models;


import androidx.annotation.NonNull;

import java.util.UUID;

import lu.circl.mispbump.auxiliary.MispRestClient;
import lu.circl.mispbump.models.restModels.Organisation;
import lu.circl.mispbump.models.restModels.Server;


public class SyncModel {

private UUID uuid;

private Server server;
private Organisation organisation;
private Organisation remoteOrganisation;


public Server getServer() {
return server;
}

public void setServer(Server server) {
this.server = server;
}

public Organisation getOrganisation() {
return organisation;
}

public void setOrganisation(Organisation organisation) {
this.organisation = organisation;
}

public Organisation getRemoteOrganisation() {
return remoteOrganisation;
}

public void setRemoteOrganisation(Organisation remoteOrganisation) {
this.remoteOrganisation = remoteOrganisation;
}


public static void createFromServer(MispRestClient mispRestClient, Server server, InitializeWithServerObject callback) {
SyncModel syncModel = new SyncModel();

syncModel.server = server;

mispRestClient.getOrganisation(server.getOrg_id(), new MispRestClient.OrganisationCallback() {
@Override
public void success(Organisation organisation) {
syncModel.organisation = organisation;

mispRestClient.getOrganisation(server.getRemote_org_id(), new MispRestClient.OrganisationCallback() {
@Override
public void success(Organisation organisation) {
syncModel.remoteOrganisation = organisation;

callback.success(syncModel);
}

@Override
public void failure(String error) {
callback.failure(error);
}
});
}

@Override
public void failure(String error) {
callback.failure(error);
}
});
}


@NonNull
@Override
public String toString() {
return server.toString() + "\n" + organisation.toString() + "\n" + remoteOrganisation.toString();
}

public interface InitializeWithServerObject {
void success(SyncModel syncModel);

void failure(String error);
}
}

+ 0
- 130
app/src/main/java/lu/circl/mispbump/models/UploadInformation.java 파일 보기

@@ -1,130 +0,0 @@
package lu.circl.mispbump.models;


import androidx.annotation.NonNull;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.UUID;


public class UploadInformation {

public enum SyncStatus {
COMPLETE,
FAILURE,
PENDING
}

private UUID uuid;

private SyncStatus currentSyncStatus = SyncStatus.PENDING;

private boolean allowSelfSigned, pull, push, cached;

private SyncInformation local;
private SyncInformation remote;

private Date date;

public UploadInformation() {
uuid = UUID.randomUUID();
date = Calendar.getInstance().getTime();
}

// 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 getUuid() {
return uuid;
}

public void setUuid(UUID uuid) {
this.uuid = uuid;
}

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 +
'}';
}
}

+ 24
- 27
app/src/main/java/lu/circl/mispbump/models/restModels/Organisation.java 파일 보기

@@ -3,6 +3,8 @@ package lu.circl.mispbump.models.restModels;

import androidx.annotation.NonNull;

import java.util.Arrays;


/**
* Information gathered from Misp API about a organisation.
@@ -20,22 +22,18 @@ public class Organisation {
private String description;
private Boolean local;
private String uuid;
// private String[] restricted_to_domain;
private String[] restricted_to_domain;
private String created_by;
private Integer user_count;

public Organisation() {
}

public Organisation toSyncOrganisation() {
Organisation organisation = new Organisation();
organisation.local = true; // TODO REMOVE FROME HERE!
organisation.name = name;
organisation.uuid = uuid;
organisation.description = description;
organisation.nationality = nationality;
organisation.sector = sector;
organisation.type = "Sync organisation";
organisation.contacts = contacts;

return organisation;
@@ -130,13 +128,13 @@ public class Organisation {
this.uuid = uuid;
}

// public String[] getRestricted_to_domain() {
// return restricted_to_domain;
// }
public String[] getRestricted_to_domain() {
return restricted_to_domain;
}

// public void setRestricted_to_domain(String[] restricted_to_domain) {
// this.restricted_to_domain = restricted_to_domain;
// }
public void setRestricted_to_domain(String[] restricted_to_domain) {
this.restricted_to_domain = restricted_to_domain;
}

public String getCreated_by() {
return created_by;
@@ -157,21 +155,20 @@ public class Organisation {
@NonNull
@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='" + Arrays.toString(restricted_to_domain) + '\'' +
", created_by='" + created_by + '\'' +
", user_count=" + user_count +
'}';
return "Organisation: \n" +
"\t id = " + id + "\n" +
"\t name = " + name + '\n' +
"\t date_created = " + date_created + '\n' +
"\t date_modified = " + date_modified + '\n' +
"\t type = " + type + '\n' +
"\t nationality = " + nationality + '\n' +
"\t sector = " + sector + '\n' +
"\t contacts = " + contacts + '\n' +
"\t description = " + description + '\n' +
"\t local = " + local + '\n' +
"\t uuid = " + uuid + '\n' +
"\t restricted_to_domain = " + Arrays.toString(restricted_to_domain) + '\n' +
"\t created_by = " + created_by + '\n' +
"\t user_count = " + user_count;
}
}

+ 34
- 30
app/src/main/java/lu/circl/mispbump/models/restModels/Server.java 파일 보기

@@ -11,25 +11,30 @@ public class Server {
private String url;
private String authkey;
private Integer org_id;
private Boolean push;
private Boolean pull;
private Boolean push = false;
private Boolean pull = false;
private Object lastpulledid;
private Object lastpushedid;
private Object organization;
private Integer remote_org_id;
private Boolean publish_without_email;
private Boolean unpublish_event;
private Boolean self_signed;
private Boolean publish_without_email = false;
private Boolean unpublish_event = false;
private Boolean self_signed = false;
private String pull_rules;
private String push_rules;
private Object cert_file;
private Object client_cert_file;
private Boolean internal;
private Boolean skip_proxy;
private Boolean caching_enabled;
private Boolean caching_enabled = false;
private Boolean cache_timestamp;


public Server(String url) {
this.url = url;
}


public Integer getId() {
return id;
}
@@ -209,29 +214,28 @@ public class Server {
@NonNull
@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 +
'}';
return "Server: \n" +
"\t id = " + id + '\n' +
"\t name = " + name + '\n' +
"\t url = " + url + '\n' +
"\t authkey = " + authkey + '\n' +
"\t org_id = " + org_id + '\n' +
"\t push = " + push + '\n' +
"\t pull = " + pull + '\n' +
"\t lastpulledid = " + lastpulledid + '\n' +
"\t lastpushedid = " + lastpushedid + '\n' +
"\t organization = " + organization + '\n' +
"\t remote_org_id = " + remote_org_id + '\n' +
"\t publish_without_email = " + publish_without_email + '\n' +
"\t unpublish_event = " + unpublish_event + '\n' +
"\t self_signed = " + self_signed + '\n' +
"\t pull_rules = " + pull_rules + '\n' +
"\t push_rules = " + push_rules + '\n' +
"\t cert_file = " + cert_file + '\n' +
"\t client_cert_file = " + client_cert_file + '\n' +
"\t internal = " + internal + '\n' +
"\t skip_proxy = " + skip_proxy + '\n' +
"\t caching_enabled = " + caching_enabled + '\n' +
"\t cache_timestamp = " + cache_timestamp;
}
}

+ 35
- 24
app/src/main/java/lu/circl/mispbump/models/restModels/User.java 파일 보기

@@ -3,6 +3,8 @@ package lu.circl.mispbump.models.restModels;

import androidx.annotation.NonNull;

import lu.circl.mispbump.auxiliary.RandomString;


public class User {

@@ -30,6 +32,16 @@ public class User {
private String date_modified;


public User toSyncUser() {
User user = new User();
user.email = email;
user.authkey = new RandomString(40).nextString();
user.password = new RandomString(16).nextString();

return user;
}


public Integer getId() {
return id;
}
@@ -210,29 +222,28 @@ public class User {
@NonNull
@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 + '\'' +
'}';
return "User: \n" +
"\t id = " + id + '\n' +
"\t password = " + password + '\n' +
"\t org_id = " + org_id + '\n' +
"\t email = " + email + '\n' +
"\t autoalert = " + autoalert + '\n' +
"\t authkey = " + authkey + '\n' +
"\t invited_by = " + invited_by + '\n' +
"\t gpgkey = " + gpgkey + '\n' +
"\t certif_public = " + certif_public + '\n' +
"\t nids_sid = " + nids_sid + '\n' +
"\t termsaccepted = " + termsaccepted + '\n' +
"\t newsread = " + newsread + '\n' +
"\t role_id = " + role_id + '\n' +
"\t change_pw = " + change_pw + '\n' +
"\t contactalert = " + contactalert + '\n' +
"\t disabled = " + disabled + '\n' +
"\t expiration = " + expiration + '\n' +
"\t current_login = " + current_login + '\n' +
"\t last_login = " + last_login + '\n' +
"\t force_logout = " + force_logout + '\n' +
"\t date_created = " + date_created + '\n' +
"\t date_modified = " + date_modified;
}
}

+ 1
- 1
app/src/main/java/lu/circl/mispbump/security/KeyStoreWrapper.java 파일 보기

@@ -32,7 +32,7 @@ public class 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 USER_CREDENTIALS_ALIAS = "ALIAS_USER_CREDENTIALS";
public static final String UPLOAD_INFORMATION_ALIAS = "ALIAS_UPLOAD_INFORMATION";
public static final String SYNC_INFORMATION_ALIAS = "ALIAS_UPLOAD_INFORMATION";

private static final String KEYSTORE_PROVIDER = "AndroidKeyStore";
private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";


+ 9
- 0
app/src/main/res/drawable/ic_arrow_down.xml 파일 보기

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M8.12,9.29L12,13.17l3.88,-3.88c0.39,-0.39 1.02,-0.39 1.41,0 0.39,0.39 0.39,1.02 0,1.41l-4.59,4.59c-0.39,0.39 -1.02,0.39 -1.41,0L6.7,10.7c-0.39,-0.39 -0.39,-1.02 0,-1.41 0.39,-0.38 1.03,-0.39 1.42,0z"/>
</vector>

+ 9
- 0
app/src/main/res/drawable/ic_cloud_download.xml 파일 보기

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M38.71,20.07C37.35,13.19 31.28,8 24,8c-5.78,0 -10.79,3.28 -13.3,8.07C4.69,16.72 0,21.81 0,28c0,6.63 5.37,12 12,12h26c5.52,0 10,-4.48 10,-10 0,-5.28 -4.11,-9.56 -9.29,-9.93zM34,26L24,36 14,26h6v-8h8v8h6z"/>
</vector>

+ 6
- 0
app/src/main/res/drawable/tooltip_background.xml 파일 보기

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/white"/>
<corners android:radius="4dp"/>
</shape>

+ 8
- 0
app/src/main/res/layout/activity_network_test.xml 파일 보기

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activities.NetworkTestActivity">

</androidx.constraintlayout.widget.ConstraintLayout>

+ 289
- 0
app/src/main/res/layout/activity_sync_info_detail.xml 파일 보기

@@ -0,0 +1,289 @@
<?xml version="1.0" encoding="utf-8"?>

<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activities.SyncInfoDetailActivity"
android:orientation="vertical">

<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:theme="@style/ToolbarTheme"
app:popupTheme="@style/PopupTheme" />
</com.google.android.material.appbar.AppBarLayout>

<ScrollView
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
android:layout_width="match_parent"
android:layout_height="match_parent">

<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<lu.circl.expandablecardview.ExpandableCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:card_header_background_color="@color/colorPrimary"
app:card_header_foreground_color="@color/white"
app:card_icon="@drawable/ic_info_outline"
app:card_title="Information">

<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<lu.circl.mispbump.customViews.MaterialPreferenceText
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:title="Name"
app:subtitle="No name" />

<lu.circl.mispbump.customViews.MaterialPreferenceText
android:id="@+id/uuid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/name"
app:layout_constraintEnd_toEndOf="parent"
app:title="UUID"
app:subtitle="No UUID" />

<lu.circl.mispbump.customViews.MaterialPreferenceText
android:id="@+id/sector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/uuid"
app:layout_constraintEnd_toEndOf="parent"
app:title="Sector"
app:subtitle="No sector" />

<lu.circl.mispbump.customViews.MaterialPreferenceText
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/uuid"
app:layout_constraintEnd_toEndOf="parent"
app:title="@string/description"
app:subtitle="No description" />
</LinearLayout>

</lu.circl.expandablecardview.ExpandableCardView>


<lu.circl.expandablecardview.ExpandableCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:card_header_background_color="@color/colorPrimary"
app:card_header_foreground_color="@color/white"
app:card_icon="@drawable/ic_settings"
app:card_title="@string/settings">

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">

<CheckBox
android:id="@+id/checkbox_self_signed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="@string/settings_self_signed_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<CheckBox
android:id="@+id/checkbox_push"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:text="@string/settings_push_title"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/checkbox_self_signed" />

<CheckBox
android:id="@+id/checkbox_pull"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="8dp"
android:text="@string/settings_pull_title"
app:layout_constraintStart_toEndOf="@+id/checkbox_push"
app:layout_constraintTop_toBottomOf="@id/checkbox_self_signed" />

<CheckBox
android:id="@+id/checkbox_cache"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:text="@string/settings_cache_title"
app:layout_constraintTop_toBottomOf="@id/checkbox_push"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

</lu.circl.expandablecardview.ExpandableCardView>


<lu.circl.expandablecardview.ExpandableCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:card_header_background_color="@color/colorPrimary"
app:card_header_foreground_color="@color/white"
app:card_icon="@drawable/ic_verified_user"
app:card_title="@string/credentials">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">

<lu.circl.mispbump.customViews.MaterialPreferenceText
android:id="@+id/email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:title="Email"
app:subtitle="Keine Ahnung" />

<lu.circl.mispbump.customViews.MaterialPasswordView
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:title="Password"
app:password="Weiß ich leider auch nicht" />

<lu.circl.mispbump.customViews.MaterialPasswordView
android:id="@+id/authkey"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:title="Authkey"
app:password="Den erst recht nicht ..." />

</LinearLayout>

</lu.circl.expandablecardview.ExpandableCardView>

<View
android:layout_width="match_parent"
android:layout_height="86dp"/>

</LinearLayout>

</ScrollView>

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_gravity="bottom|end"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false">

<!-- <FrameLayout-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="match_parent"-->
<!-- android:background="@color/black_50"/>-->

<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_keyboard_arrow_down"
android:tint="@color/white"
android:layout_margin="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>

<LinearLayout
android:id="@+id/layout_upload"
android:visibility="visible"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:padding="8dp"
android:clipToPadding="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/fab_main">

<TextView
android:text="Upload to MISP instance"
android:textColor="@color/white"
android:padding="8dp"
android:background="@drawable/tooltip_background"
android:backgroundTint="@color/black_70"
android:layout_gravity="center_vertical"
android:layout_marginEnd="16dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_upload"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_cloud_upload"
app:fabSize="mini"/>
</LinearLayout>

<LinearLayout
android:visibility="visible"
android:id="@+id/layout_download"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:padding="8dp"
android:clipToPadding="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/layout_upload">

<TextView
android:text="Download changes from MISP instance"
android:textColor="@color/white"
android:padding="8dp"
android:background="@drawable/tooltip_background"
android:backgroundTint="@color/black_70"
android:layout_gravity="center_vertical"
android:layout_marginEnd="16dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_download"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_cloud_download"
app:fabSize="mini"/>
</LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

+ 57
- 58
app/src/main/res/layout/activity_upload.xml 파일 보기

@@ -6,7 +6,6 @@
android:id="@+id/rootLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
tools:context=".activities.UploadActivity">

<com.google.android.material.appbar.AppBarLayout
@@ -17,78 +16,78 @@
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
android:background="@color/colorPrimary">

<TextView
android:id="@+id/toolbarTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"
android:text="@string/upload"
android:layout_gravity="center"/>

</androidx.appcompat.widget.Toolbar>
android:theme="@style/ToolbarTheme"
app:popupTheme="@style/PopupTheme"/>
</com.google.android.material.appbar.AppBarLayout>

<androidx.appcompat.widget.LinearLayoutCompat

<LinearLayout
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true">
android:layout_height="wrap_content"
android:orientation="vertical">

<lu.circl.mispbump.customViews.UploadAction
android:id="@+id/availableAction"
<lu.circl.mispbump.customViews.ProgressActionView
android:layout_marginTop="8dp"
android:id="@+id/availableProgressAction"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="@color/white"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:description="@string/upload_action_available"/>
app:action_title="Instance available"
app:action_done_icon="@drawable/ic_check_outline"
app:action_error_icon="@drawable/ic_error_outline"
app:action_pending_icon="@drawable/ic_pending"/>

<lu.circl.mispbump.customViews.UploadAction
android:id="@+id/orgAction"
<View
android:background="@color/black_10"
android:layout_margin="8dp"
android:layout_width="match_parent"
android:layout_height="1dp"/>

<lu.circl.mispbump.customViews.ProgressActionView
android:id="@+id/organisationProgressAction"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="@color/white"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:description="@string/upload_action_add_org"/>
app:action_title="Add Organisation"
app:action_done_icon="@drawable/ic_check_outline"
app:action_error_icon="@drawable/ic_error_outline"
app:action_pending_icon="@drawable/ic_pending"/>

<View
android:background="@color/black_10"
android:layout_margin="8dp"
android:layout_width="match_parent"
android:layout_height="1dp"/>

<lu.circl.mispbump.customViews.UploadAction
android:id="@+id/userAction"
<lu.circl.mispbump.customViews.ProgressActionView
android:id="@+id/userProgressAction"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="@color/white"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:description="@string/upload_action_add_user"/>
app:action_title="Add Sync User"
app:action_done_icon="@drawable/ic_check_outline"
app:action_error_icon="@drawable/ic_error_outline"
app:action_pending_icon="@drawable/ic_pending"/>

<View
android:background="@color/black_10"
android:layout_margin="8dp"
android:layout_width="match_parent"
android:layout_height="1dp"/>

<lu.circl.mispbump.customViews.UploadAction
android:id="@+id/serverAction"
<lu.circl.mispbump.customViews.ProgressActionView
android:id="@+id/serverProgressAction"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="@color/white"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:description="@string/upload_action_add_server"/>
</androidx.appcompat.widget.LinearLayoutCompat>
app:action_title="Add Sync Server"
app:action_done_icon="@drawable/ic_check_outline"
app:action_error_icon="@drawable/ic_error_outline"
app:action_pending_icon="@drawable/ic_pending"/>

<View
android:background="@color/black_10"
android:layout_margin="8dp"
android:layout_width="match_parent"
android:layout_height="1dp"/>

</LinearLayout>

<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:src="@drawable/ic_check"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

+ 0
- 51
app/src/main/res/layout/fragment_upload_credentials.xml 파일 보기

@@ -1,51 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/rootLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">

<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false">

<lu.circl.mispbump.customViews.MaterialPreferenceText
android:id="@+id/baseUrl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@color/white"
app:subtitle="www.google.de"
app:title="@string/url"/>

<lu.circl.mispbump.customViews.MaterialPreferenceText
android:id="@+id/email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@color/white"
app:subtitle="syncuser_orga@mispx.yz"
app:title="@string/email"/>

<lu.circl.mispbump.customViews.MaterialPasswordView
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@color/white"
app:title="@string/password"
app:password="abc"/>

<lu.circl.mispbump.customViews.MaterialPasswordView
android:id="@+id/authkey"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@color/white"
app:title="@string/authkey"
app:password="abc"/>
</LinearLayout>
</ScrollView>

+ 0
- 75
app/src/main/res/layout/fragment_upload_settings.xml 파일 보기

@@ -1,75 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/rootLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">

<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:text="@string/fragment_upload_settings_title"
android:textAppearance="@style/TextAppearance.MaterialComponents.Overline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>

<lu.circl.mispbump.customViews.MaterialPreferenceSwitch
android:id="@+id/self_signed_switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text"
app:onText="@string/settings_self_signed_on"
app:offText="@string/settings_self_signed_off"
app:title="@string/settings_self_signed_title"/>

<lu.circl.mispbump.customViews.MaterialPreferenceSwitch
android:id="@+id/push_switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/self_signed_switch"
app:onText="@string/settings_push_on"
app:offText="@string/settings_push_off"
app:title="@string/settings_push_title"/>

<lu.circl.mispbump.customViews.MaterialPreferenceSwitch
android:id="@+id/pull_switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/push_switch"
app:onText="@string/settings_pull_on"
app:offText="@string/settings_pull_off"
app:title="@string/settings_pull_title"/>

<lu.circl.mispbump.customViews.MaterialPreferenceSwitch
android:id="@+id/cache_switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/pull_switch"
app:onText="@string/settings_cache_on"
app:offText="@string/settings_cache_off"
app:title="@string/settings_cache_title"/>

</androidx.constraintlayout.widget.ConstraintLayout>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

+ 2
- 14
app/src/main/res/layout/material_password_view.xml 파일 보기

@@ -24,25 +24,13 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/>

<ImageButton
android:id="@+id/copy"
android:layout_width="48dp"
android:layout_height="0dp"
android:layout_marginEnd="8dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_file_copy"
android:tint="@color/colorIconDark"
app:layout_constraintBottom_toBottomOf="@+id/material_password"
app:layout_constraintEnd_toStartOf="@id/visibleToggle"
app:layout_constraintTop_toTopOf="parent"/>

<TextView
android:id="@+id/material_password_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:textAppearance="@style/AppTheme.TextAppearance.ListTitle"
app:layout_constraintEnd_toStartOf="@+id/copy"
app:layout_constraintEnd_toStartOf="@+id/visibleToggle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Title"/>
@@ -54,7 +42,7 @@
android:layout_marginEnd="8dp"
android:textAppearance="@style/AppTheme.TextAppearance.ListSubtitle"
android:visibility="visible"
app:layout_constraintEnd_toStartOf="@+id/copy"
app:layout_constraintEnd_toStartOf="@+id/visibleToggle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/material_password_title"
tools:ignore="TextViewEdits"


+ 39
- 28
app/src/main/res/layout/row_upload_information.xml 파일 보기

@@ -7,54 +7,65 @@
android:id="@+id/rootLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:foreground="?attr/selectableItemBackgroundBorderless"
android:layout_marginBottom="1dp"
android:padding="8dp">
android:foreground="?attr/selectableItemBackgroundBorderless">

<ImageView
android:id="@+id/syncStatus"
android:transitionName="icon"
android:transformPivotX="12dp"
android:transformPivotY="12dp"
<TextView
android:id="@+id/date_month"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_margin="16dp"
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:textSize="14sp"
tools:text="Aug"/>

<TextView
android:id="@+id/date_day"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginBottom="16dp"
android:tint="@color/status_red"
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_error_outline"/>
app:layout_constraintTop_toBottomOf="@id/date_month"
app:layout_constraintStart_toStartOf="@id/date_month"
app:layout_constraintEnd_toEndOf="@id/date_month"
tools:text="16"/>

<TextView
android:id="@+id/orgName"
android:transitionName="title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="16dp"
android:textAppearance="@style/AppTheme.TextAppearance.ListTitle"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/date"
app:layout_constraintStart_toEndOf="@+id/syncStatus"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@id/date_month"
tools:text="Organisation A"/>

<TextView
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
<ImageView
android:id="@+id/syncStatus"
android:src="@drawable/ic_check_outline"
android:tint="@color/status_green"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_margin="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<View
android:background="@color/black_10"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginStart="16dp"
app:layout_constraintStart_toEndOf="@id/date_month"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="20.3.2019"/>
app:layout_constraintBottom_toBottomOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

+ 5
- 4
app/src/main/res/layout/view_upload_action.xml 파일 보기

@@ -10,7 +10,7 @@
android:animateLayoutChanges="true">

<ImageView
android:id="@+id/stateView"
android:id="@+id/progressIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_error_outline"
@@ -19,8 +19,8 @@
app:layout_constraintTop_toTopOf="parent"/>

<ProgressBar
android:visibility="gone"
android:id="@+id/progressBar"
android:visibility="gone"
android:layout_width="24dp"
android:layout_height="24dp"
app:layout_constraintEnd_toEndOf="parent"
@@ -33,18 +33,19 @@
android:layout_marginTop="2dp"
android:layout_marginEnd="16dp"
android:textAppearance="@style/AppTheme.TextAppearance.ListTitle"
app:layout_constraintEnd_toStartOf="@+id/stateView"
app:layout_constraintEnd_toStartOf="@+id/progressIcon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Add Organisation"/>

<TextView
android:visibility="gone"
android:id="@+id/error"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="@style/AppTheme.TextAppearance.ListSubtitle"
android:textColor="@color/status_red"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title"
tools:text="Organisation B added successfully"/>


+ 1
- 0
app/src/main/res/values-de/strings.xml 파일 보기

@@ -49,4 +49,5 @@
<string name="preference_category_general">Allgemein</string>
<string name="preference_github_summary">Besuchen Sie das Github Projekt</string>
<string name="preference_category_information">App Informationen</string>
<string name="sync_details_activity_label">Synchronisations Details</string>
</resources>

+ 5
- 2
app/src/main/res/values/attrs.xml 파일 보기

@@ -21,8 +21,11 @@
<attr name="password"/>
</declare-styleable>

<declare-styleable name="UploadAction">
<attr name="description" format="string"/>
<declare-styleable name="ProgressActionView">
<attr name="action_title" format="string"/>
<attr name="action_pending_icon" format="reference"/>
<attr name="action_done_icon" format="reference"/>
<attr name="action_error_icon" format="reference"/>
</declare-styleable>

<declare-styleable name="FixedAspectRatioFrameLayout">


+ 4
- 0
app/src/main/res/values/colors.xml 파일 보기

@@ -17,6 +17,10 @@
<color name="grey_light">#F0F0F0</color>

<color name="black">#000</color>
<color name="black_70">#B3000000</color>
<color name="black_50">#80000000</color>
<color name="black_30">#4D000000</color>
<color name="black_10">#1A000000</color>

<color name="status_green">#4CAF50</color>
<color name="status_amber">#FB8C00</color>


+ 1
- 0
app/src/main/res/values/strings.xml 파일 보기

@@ -55,4 +55,5 @@
<string name="preference_category_general">General</string>
<string name="preference_category_information">App information</string>
<string name="preference_github_summary">Visit the Github project</string>
<string name="sync_details_activity_label">Synchronisation details</string>
</resources>

+ 2
- 2
build.gradle 파일 보기

@@ -1,14 +1,14 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
ext.kotlin_version = '1.3.31'
ext.kotlin_version = '1.3.41'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.2'
classpath 'com.android.tools.build:gradle:3.5.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

// NOTE: Do not place your application dependencies here; they belong


+ 1
- 0
expandablecardview/.gitignore 파일 보기

@@ -0,0 +1 @@
/build

+ 34
- 0
expandablecardview/build.gradle 파일 보기

@@ -0,0 +1,34 @@
apply plugin: 'com.android.library'

android {
compileSdkVersion 29
buildToolsVersion "29.0.1"


defaultConfig {
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}

}

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])

implementation 'androidx.appcompat:appcompat:1.0.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

+ 21
- 0
expandablecardview/proguard-rules.pro 파일 보기

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

+ 29
- 0
expandablecardview/src/androidTest/java/lu/circl/expandablecardview/ExampleInstrumentedTest.java 파일 보기

@@ -0,0 +1,29 @@
package lu.circl.expandablecardview;


import android.content.Context;

import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;

import org.junit.Test;
import org.junit.runner.RunWith;

import static org.junit.Assert.assertEquals;


/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();

assertEquals("lu.circl.expandablecardview.test", appContext.getPackageName());
}
}

+ 1
- 0
expandablecardview/src/main/AndroidManifest.xml 파일 보기

@@ -0,0 +1 @@
<manifest package="lu.circl.expandablecardview" />

+ 177
- 0
expandablecardview/src/main/java/lu/circl/expandablecardview/ExpandableCardView.java 파일 보기

@@ -0,0 +1,177 @@
package lu.circl.expandablecardview;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.GradientDrawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;


public class ExpandableCardView extends LinearLayout {

private Context context;

private FrameLayout contentLayout;
private int cardContentPadding;

private boolean isExpanded = true;
private int animationSpeed = 200;


public ExpandableCardView(Context context) {
this(context, null);
}

public ExpandableCardView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public ExpandableCardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;

setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
setOrientation(VERTICAL);
setClipToOutline(true);

TypedArray customAttributes = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ExpandableCardView, defStyleAttr, 0);

// general
int cornerRadius = customAttributes.getDimensionPixelSize(R.styleable.ExpandableCardView_card_corner_radius, 12);

// header
String cardTitle = customAttributes.getString(R.styleable.ExpandableCardView_card_title);
int iconRes = customAttributes.getResourceId(R.styleable.ExpandableCardView_card_icon, 0x0);
int headerForegroundColor = customAttributes.getColor(R.styleable.ExpandableCardView_card_header_foreground_color, 0xFF000000);
int headerBackgroundColor = customAttributes.getColor(R.styleable.ExpandableCardView_card_header_background_color, 0xFFFFFFFF);

// content
cardContentPadding = customAttributes.getDimensionPixelSize(R.styleable.ExpandableCardView_card_content_padding, 0);
int cardContentBackgroundColor = customAttributes.getColor(R.styleable.ExpandableCardView_card_content_background_color, 0xFFFFFFFF);

customAttributes.recycle();

GradientDrawable cardBackground = new GradientDrawable();
cardBackground.setCornerRadius(cornerRadius);
cardBackground.setColor(cardContentBackgroundColor);

setBackground(cardBackground);
setElevation(10);

initHeader(cardTitle, iconRes, headerBackgroundColor, headerForegroundColor);
}

@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (getChildCount() == 0) {
super.addView(child, index, params); // add header
} else {
if (contentLayout == null) {
contentLayout = new FrameLayout(context);
contentLayout.setLayoutParams(new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
contentLayout.setPadding(cardContentPadding, cardContentPadding, cardContentPadding, cardContentPadding);

super.addView(contentLayout, index, new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
}

contentLayout.addView(child);
}
}


private void initHeader(String title, int iconRes, int backgroundColor, int foregroundColor) {
View header = LayoutInflater.from(context).inflate(R.layout.expandable_card_view_header, this, true);
LinearLayout ll = header.findViewById(R.id.llRoot);
ll.setBackgroundColor(backgroundColor);

TextView titleTextView = header.findViewById(R.id.expandable_card_view_header_title);
titleTextView.setText(title);
titleTextView.setTextColor(foregroundColor);

ImageView iconView = header.findViewById(R.id.expandable_card_view_header_icon);
if (iconRes == 0x0) {
iconView.setVisibility(GONE);
} else {
iconView.setImageResource(iconRes);
iconView.setColorFilter(foregroundColor);
}

final ImageButton expandToggle = header.findViewById(R.id.expandable_card_view_header_toggle);
expandToggle.setColorFilter(foregroundColor);
expandToggle.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
if (isExpanded) {
collapse(contentLayout);
expandToggle.animate().rotation(0).setDuration(animationSpeed);
} else {
expand(contentLayout);
expandToggle.animate().rotation(180).setDuration(animationSpeed);
}

isExpanded = !isExpanded;
}
});
}

private void expand(final View v) {
int matchParentMeasureSpec = View.MeasureSpec.makeMeasureSpec(((View) v.getParent()).getWidth(), View.MeasureSpec.EXACTLY);
int wrapContentMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
v.measure(matchParentMeasureSpec, wrapContentMeasureSpec);
final int targetHeight = v.getMeasuredHeight();

v.getLayoutParams().height = 1;
v.setVisibility(View.VISIBLE);
Animation a = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
v.getLayoutParams().height = interpolatedTime == 1
? LayoutParams.WRAP_CONTENT
: (int) (targetHeight * interpolatedTime);
v.requestLayout();
}

@Override
public boolean willChangeBounds() {
return true;
}
};

a.setDuration(animationSpeed);
v.startAnimation(a);
}

private void collapse(final View v) {
final int initialHeight = v.getMeasuredHeight();

Animation a = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if (interpolatedTime == 1) {
v.setVisibility(View.GONE);
} else {
v.getLayoutParams().height = initialHeight - (int) (initialHeight * interpolatedTime);
v.requestLayout();
}
}

@Override
public boolean willChangeBounds() {
return true;
}
};

a.setDuration(animationSpeed);
v.startAnimation(a);
}

}

+ 9
- 0
expandablecardview/src/main/res/drawable/ic_info_outline.xml 파일 보기

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
</vector>

+ 9
- 0
expandablecardview/src/main/res/drawable/ic_keyboard_arrow_down.xml 파일 보기

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M7.41,7.84L12,12.42l4.59,-4.58L18,9.25l-6,6 -6,-6z"/>
</vector>

+ 37
- 0
expandablecardview/src/main/res/layout/expandable_card_view_header.xml 파일 보기

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/llRoot"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<ImageView
android:id="@+id/expandable_card_view_header_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
tools:src="@drawable/ic_info_outline"/>

<TextView
android:id="@+id/expandable_card_view_header_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="16dp"
android:layout_gravity="center_vertical"
tools:text="Card Title"/>

<ImageButton
android:id="@+id/expandable_card_view_header_toggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:padding="8dp"
android:src="@drawable/ic_keyboard_arrow_down"
android:background="?attr/selectableItemBackgroundBorderless"/>

</LinearLayout>

+ 17
- 0
expandablecardview/src/main/res/values/attrs.xml 파일 보기

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ExpandableCardView">
// general
<attr name="card_corner_radius" format="dimension"/>

// header
<attr name="card_title" format="string"/>
<attr name="card_icon" format="reference"/>
<attr name="card_header_background_color" format="color"/>
<attr name="card_header_foreground_color" format="color"/>

// content
<attr name="card_content_padding" format="dimension"/>
<attr name="card_content_background_color" format="color"/>
</declare-styleable>
</resources>

+ 3
- 0
expandablecardview/src/main/res/values/strings.xml 파일 보기

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">ExpandableCardView</string>
</resources>

+ 19
- 0
expandablecardview/src/test/java/lu/circl/expandablecardview/ExampleUnitTest.java 파일 보기

@@ -0,0 +1,19 @@
package lu.circl.expandablecardview;


import org.junit.Test;

import static org.junit.Assert.assertEquals;


/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}

+ 2
- 2
gradle/wrapper/gradle-wrapper.properties 파일 보기

@@ -1,6 +1,6 @@
#Wed May 08 12:00:08 CEST 2019
#Wed Aug 21 13:50:22 CEST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip

+ 1
- 1
settings.gradle 파일 보기

@@ -1 +1 @@
include ':app'
include ':app', ':expandablecardview'

불러오는 중...
취소
저장