Merge pull request #8 from MISP/development

Development
master
Felix PK 2019-10-06 20:42:57 +02:00 committed by GitHub
commit fad2d28221
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
164 changed files with 4158 additions and 2443 deletions

View File

@ -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>

View File

@ -1,4 +1,6 @@
# MISPbump # MISPbump
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/72e7c12910284125b6971bb9d9c08229)](https://www.codacy.com/app/felixpk/misp-bump?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=MISP/misp-bump&amp;utm_campaign=Badge_Grade)
Simple and secure synchronisation of MISP instances Simple and secure synchronisation of MISP instances
# What is MISPbump? # What is MISPbump?
@ -45,4 +47,4 @@ After that the two MISP instances are connected.
# Dependencies # Dependencies
+ [Retrofit](https://github.com/square/retrofit) + [Retrofit](https://github.com/square/retrofit)
+ [ZXing](https://github.com/zxing/zxing) + [ZXing](https://github.com/zxing/zxing)

View File

@ -1 +0,0 @@
theme: jekyll-theme-minimal

View File

@ -3,11 +3,11 @@ apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
android { android {
compileSdkVersion 28 compileSdkVersion 29
defaultConfig { defaultConfig {
applicationId "lu.circl.mispbump" applicationId "lu.circl.mispbump"
minSdkVersion 23 minSdkVersion 23
targetSdkVersion 28 targetSdkVersion 29
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -19,6 +19,15 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
} }
compileOptions {
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
}
buildToolsVersion = '29.0.1'
}
repositories {
mavenCentral()
} }
dependencies { dependencies {
@ -29,28 +38,23 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.0.0' implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'androidx.preference:preference:1.1.0-rc01'
// retrofit // retrofit
implementation 'com.squareup.retrofit2:retrofit:2.6.0' implementation 'com.squareup.retrofit2:retrofit:2.6.1'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0' implementation 'com.squareup.retrofit2:converter-gson:2.6.1'
implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0' implementation 'com.squareup.okhttp3:logging-interceptor:4.1.0'
// barcode reading // barcode reading
implementation 'com.google.android.gms:play-services-vision:17.0.2' implementation 'com.google.android.gms:play-services-vision:18.0.0'
// barcode generation // barcode generation
implementation 'com.journeyapps:zxing-android-embedded:3.2.0@aar' implementation 'com.journeyapps:zxing-android-embedded:3.2.0@aar'
implementation 'com.google.zxing:core:3.4.0' implementation 'com.google.zxing:core:3.4.0'
// external
implementation 'me.saket:inboxrecyclerview:1.0.0-rc1'
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
} }
repositories {
mavenCentral()
}

View File

@ -3,9 +3,6 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="lu.circl.mispbump"> package="lu.circl.mispbump">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<application <application
android:allowBackup="false" android:allowBackup="false"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
@ -15,7 +12,8 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning"> tools:ignore="GoogleAppIndexingWarning">
<activity android:name=".activities.StartUpActivity"> <activity android:name=".activities.UploadActivity" />
<activity android:name=".activities.LauncherActivity">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -35,16 +33,13 @@
android:screenOrientation="portrait" android:screenOrientation="portrait"
android:theme="@style/AppTheme.Translucent" /> android:theme="@style/AppTheme.Translucent" />
<activity <activity
android:name=".activities.UploadInfoActivity" android:name=".activities.SyncInfoDetailActivity"
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"
android:parentActivityName=".activities.HomeActivity" /> android:label="@string/sync_details_activity_label"
<activity
android:name=".activities.UploadActivity"
android:configChanges="orientation|screenSize"
android:label="Upload"
android:parentActivityName=".activities.HomeActivity" /> android:parentActivityName=".activities.HomeActivity" />
<activity <activity
android:name=".activities.PreferenceActivity" android:name=".activities.PreferenceActivity"
android:label="@string/settings"
android:parentActivityName=".activities.HomeActivity" /> android:parentActivityName=".activities.HomeActivity" />
<activity <activity
android:name=".activities.ProfileActivity" android:name=".activities.ProfileActivity"
@ -52,5 +47,8 @@
android:parentActivityName=".activities.HomeActivity" android:parentActivityName=".activities.HomeActivity"
android:theme="@style/AppTheme.Translucent" /> android:theme="@style/AppTheme.Translucent" />
</application> </application>
<uses-permission android:name="android.permission.CAMERA" />
</manifest> <uses-permission android:name="android.permission.INTERNET" />
</manifest>

View File

@ -1,11 +1,11 @@
package lu.circl.mispbump.activities; package lu.circl.mispbump.activities;
import android.content.Intent; import android.content.Intent;
import android.content.res.ColorStateList; import android.content.res.ColorStateList;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
@ -22,24 +22,22 @@ import com.google.gson.JsonSyntaxException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidKeySpecException;
import java.util.List;
import lu.circl.mispbump.R; import lu.circl.mispbump.R;
import lu.circl.mispbump.auxiliary.DialogManager;
import lu.circl.mispbump.auxiliary.PreferenceManager; import lu.circl.mispbump.auxiliary.PreferenceManager;
import lu.circl.mispbump.auxiliary.QrCodeGenerator; import lu.circl.mispbump.auxiliary.QrCodeGenerator;
import lu.circl.mispbump.auxiliary.RandomString;
import lu.circl.mispbump.fragments.CameraFragment; import lu.circl.mispbump.fragments.CameraFragment;
import lu.circl.mispbump.models.ExchangeInformation;
import lu.circl.mispbump.models.SyncInformation; 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; import lu.circl.mispbump.security.DiffieHellman;
public class ExchangeActivity extends AppCompatActivity { public class ExchangeActivity extends AppCompatActivity {
private PreferenceManager preferenceManager; private PreferenceManager preferenceManager;
private QrCodeGenerator qrCodeGenerator; private QrCodeGenerator qrCodeGenerator;
private DiffieHellman diffieHellman; private DiffieHellman diffieHellman;
private UploadInformation uploadInformation;
private CameraFragment cameraFragment; private CameraFragment cameraFragment;
@ -49,6 +47,8 @@ public class ExchangeActivity extends AppCompatActivity {
private ImageView qrCode; private ImageView qrCode;
private ImageButton prevButton, nextButton; private ImageButton prevButton, nextButton;
private SyncInformation syncInformation;
private Bitmap publicKeyQr, dataQr; private Bitmap publicKeyQr, dataQr;
private SyncState currentSyncState; private SyncState currentSyncState;
@ -66,9 +66,11 @@ public class ExchangeActivity extends AppCompatActivity {
initViews(); initViews();
initCamera(); initCamera();
uploadInformation = new UploadInformation();
publicKeyQr = generatePublicKeyBitmap(); publicKeyQr = generatePublicKeyBitmap();
syncInformation = new SyncInformation();
syncInformation.setLocal(generateSyncExchangeInformation());
setSyncState(SyncState.KEY_EXCHANGE); setSyncState(SyncState.KEY_EXCHANGE);
} }
@ -104,95 +106,86 @@ public class ExchangeActivity extends AppCompatActivity {
fragmentTransaction.commit(); 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() { private Bitmap generatePublicKeyBitmap() {
return qrCodeGenerator.generateQrCode(DiffieHellman.publicKeyToString(diffieHellman.getPublicKey())); return qrCodeGenerator.generateQrCode(DiffieHellman.publicKeyToString(diffieHellman.getPublicKey()));
} }
private Bitmap generateLocalSyncInfoBitmap() { private Bitmap generateLocalSyncInfoBitmap() {
uploadInformation.setLocal(generateLocalSyncInfo()); return qrCodeGenerator.generateQrCode(diffieHellman.encrypt(new Gson().toJson(syncInformation.getLocal())));
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().email;
return syncInformation;
} }
private void showQrCode(final Bitmap bitmap) { private void showQrCode(final Bitmap bitmap) {
runOnUiThread(new Runnable() { runOnUiThread(() -> {
@Override qrCode.setImageBitmap(bitmap);
public void run() { qrFrame.setVisibility(View.VISIBLE);
qrCode.setImageBitmap(bitmap);
qrFrame.setVisibility(View.VISIBLE);
}
}); });
} }
private void setSyncState(SyncState state) { private void setSyncState(SyncState state) {
currentSyncState = state; currentSyncState = state;
runOnUiThread(new Runnable() { runOnUiThread(() -> {
@Override switch (currentSyncState) {
public void run() { case KEY_EXCHANGE:
switch (currentSyncState) { prevButton.setImageDrawable(getDrawable(R.drawable.ic_close));
case KEY_EXCHANGE: prevButton.setVisibility(View.VISIBLE);
prevButton.setImageDrawable(getDrawable(R.drawable.ic_close)); nextButton.setVisibility(View.GONE);
prevButton.setVisibility(View.VISIBLE);
nextButton.setVisibility(View.GONE);
setCameraPreviewEnabled(true); setCameraPreviewEnabled(true);
showQrCode(publicKeyQr); showQrCode(publicKeyQr);
setReadQrStatus(ReadQrStatus.PENDING); setReadQrStatus(ReadQrStatus.PENDING);
scanFeedbackText.setText(R.string.scan_qr_hint); scanFeedbackText.setText(R.string.scan_qr_hint);
qrContentInfo.setText(R.string.public_key); qrContentInfo.setText(R.string.public_key);
break; break;
case KEY_EXCHANGE_DONE: case KEY_EXCHANGE_DONE:
prevButton.setImageDrawable(getDrawable(R.drawable.ic_close)); prevButton.setImageDrawable(getDrawable(R.drawable.ic_close));
prevButton.setVisibility(View.VISIBLE); prevButton.setVisibility(View.VISIBLE);
nextButton.setImageDrawable(getDrawable(R.drawable.ic_arrow_forward)); nextButton.setImageDrawable(getDrawable(R.drawable.ic_arrow_forward));
nextButton.setVisibility(View.VISIBLE); nextButton.setVisibility(View.VISIBLE);
setCameraPreviewEnabled(false); setCameraPreviewEnabled(false);
showQrCode(publicKeyQr); showQrCode(publicKeyQr);
setReadQrStatus(ReadQrStatus.SUCCESS); setReadQrStatus(ReadQrStatus.SUCCESS);
scanFeedbackText.setText(R.string.public_key_received_hint); scanFeedbackText.setText(R.string.public_key_received_hint);
qrContentInfo.setText(R.string.public_key); qrContentInfo.setText(R.string.public_key);
break; break;
case DATA_EXCHANGE: case DATA_EXCHANGE:
prevButton.setImageDrawable(getDrawable(R.drawable.ic_arrow_back)); prevButton.setImageDrawable(getDrawable(R.drawable.ic_arrow_back));
prevButton.setVisibility(View.VISIBLE); prevButton.setVisibility(View.VISIBLE);
nextButton.setVisibility(View.GONE); nextButton.setVisibility(View.GONE);
setCameraPreviewEnabled(true); setCameraPreviewEnabled(true);
showQrCode(dataQr); showQrCode(dataQr);
setReadQrStatus(ReadQrStatus.PENDING); setReadQrStatus(ReadQrStatus.PENDING);
scanFeedbackText.setText(R.string.scan_qr_hint); scanFeedbackText.setText(R.string.scan_qr_hint);
qrContentInfo.setText(R.string.sync_information); qrContentInfo.setText(R.string.sync_information);
break; break;
case DATA_EXCHANGE_DONE: case DATA_EXCHANGE_DONE:
prevButton.setImageDrawable(getDrawable(R.drawable.ic_arrow_back)); prevButton.setImageDrawable(getDrawable(R.drawable.ic_arrow_back));
prevButton.setVisibility(View.VISIBLE); prevButton.setVisibility(View.VISIBLE);
nextButton.setImageDrawable(getDrawable(R.drawable.ic_check)); nextButton.setImageDrawable(getDrawable(R.drawable.ic_check));
nextButton.setVisibility(View.VISIBLE); nextButton.setVisibility(View.VISIBLE);
setCameraPreviewEnabled(false); setCameraPreviewEnabled(false);
showQrCode(dataQr); showQrCode(dataQr);
setReadQrStatus(ReadQrStatus.SUCCESS); setReadQrStatus(ReadQrStatus.SUCCESS);
scanFeedbackText.setText(R.string.sync_info_received_hint); scanFeedbackText.setText(R.string.sync_info_received_hint);
qrContentInfo.setText(R.string.public_key); qrContentInfo.setText(R.string.public_key);
break; break;
}
} }
}); });
} }
@ -244,14 +237,12 @@ public class ExchangeActivity extends AppCompatActivity {
View view = findViewById(R.id.fragmentContainer); View view = findViewById(R.id.fragmentContainer);
if (enabled) { if (enabled) {
Log.d("DEBUG", "cameraPreview enabled");
view.animate() view.animate()
.alpha(1f) .alpha(1f)
.setDuration(250) .setDuration(250)
.start(); .start();
cameraFragment.setReadQrEnabled(true); cameraFragment.setReadQrEnabled(true);
} else { } else {
Log.d("DEBUG", "cameraPreview disabled");
view.animate() view.animate()
.alpha(0f) .alpha(0f)
.setDuration(250) .setDuration(250)
@ -262,107 +253,70 @@ public class ExchangeActivity extends AppCompatActivity {
private CameraFragment.QrScanCallback onQrScanned() { private CameraFragment.QrScanCallback onQrScanned() {
return new CameraFragment.QrScanCallback() { return qrData -> {
@Override cameraFragment.setReadQrEnabled(false);
public void qrScanResult(String qrData) {
cameraFragment.setReadQrEnabled(false);
switch (currentSyncState) { switch (currentSyncState) {
case KEY_EXCHANGE: case KEY_EXCHANGE:
try { try {
diffieHellman.setForeignPublicKey(DiffieHellman.publicKeyFromString(qrData)); diffieHellman.setForeignPublicKey(DiffieHellman.publicKeyFromString(qrData));
setSyncState(SyncState.KEY_EXCHANGE_DONE); setSyncState(SyncState.KEY_EXCHANGE_DONE);
dataQr = generateLocalSyncInfoBitmap(); dataQr = generateLocalSyncInfoBitmap();
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) { } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
if (currentReadQrStatus == ReadQrStatus.PENDING) { if (currentReadQrStatus == ReadQrStatus.PENDING) {
setReadQrStatus(ReadQrStatus.FAILURE); setReadQrStatus(ReadQrStatus.FAILURE);
Snackbar.make(rootLayout, "Public key not parsable", Snackbar.LENGTH_LONG).show(); Snackbar.make(rootLayout, "Public key not parsable", Snackbar.LENGTH_LONG).show();
}
cameraFragment.setReadQrEnabled(true);
} }
break;
case DATA_EXCHANGE:
try {
final SyncInformation remoteSyncInfo = new Gson().fromJson(diffieHellman.decrypt(qrData), SyncInformation.class);
final List<UploadInformation> uploadInformationList = preferenceManager.getUploadInformationList(); cameraFragment.setReadQrEnabled(true);
}
for (final UploadInformation ui : uploadInformationList) { break;
if (ui.getRemote().organisation.getUuid().equals(remoteSyncInfo.organisation.getUuid())) { case DATA_EXCHANGE:
DialogManager.syncAlreadyExistsDialog(ui.getRemote(), remoteSyncInfo, ExchangeActivity.this, new DialogManager.IDialogFeedback() { try {
@Override syncInformation.setRemote(new Gson().fromJson(diffieHellman.decrypt(qrData), ExchangeInformation.class));
public void positive() { preferenceManager.addSyncInformation(syncInformation);
// update remote info only setSyncState(SyncState.DATA_EXCHANGE_DONE);
uploadInformation.setUuid(ui.getUuid()); } catch (JsonSyntaxException e) {
uploadInformation.setDate(); if (currentReadQrStatus == ReadQrStatus.PENDING) {
} setReadQrStatus(ReadQrStatus.FAILURE);
Snackbar.make(rootLayout, "Sync information not parsable", Snackbar.LENGTH_LONG).show();
@Override
public void negative() {
// replace credentials too
uploadInformationList.remove(ui);
preferenceManager.setUploadInformationList(uploadInformationList);
}
});
break;
}
}
uploadInformation.setRemote(remoteSyncInfo);
preferenceManager.addUploadInformation(uploadInformation);
setSyncState(SyncState.DATA_EXCHANGE_DONE);
} catch (JsonSyntaxException e) {
if (currentReadQrStatus == ReadQrStatus.PENDING) {
setReadQrStatus(ReadQrStatus.FAILURE);
Snackbar.make(rootLayout, "Sync information not parsable", Snackbar.LENGTH_LONG).show();
}
cameraFragment.setReadQrEnabled(true);
} }
break;
} cameraFragment.setReadQrEnabled(true);
}
break;
} }
}; };
} }
private View.OnClickListener onPrevClicked() { private View.OnClickListener onPrevClicked() {
return new View.OnClickListener() { return v -> {
@Override switch (currentSyncState) {
public void onClick(View v) { case KEY_EXCHANGE:
switch (currentSyncState) { case KEY_EXCHANGE_DONE:
case KEY_EXCHANGE: // TODO warning that sync will be lost
case KEY_EXCHANGE_DONE: finish();
// TODO warning that sync will be lost break;
finish(); case DATA_EXCHANGE:
break; case DATA_EXCHANGE_DONE:
case DATA_EXCHANGE: setSyncState(SyncState.KEY_EXCHANGE_DONE);
case DATA_EXCHANGE_DONE: break;
setSyncState(SyncState.KEY_EXCHANGE_DONE);
break;
}
} }
}; };
} }
private View.OnClickListener onNextClicked() { private View.OnClickListener onNextClicked() {
return new View.OnClickListener() { return v -> {
@Override switch (currentSyncState) {
public void onClick(View v) { case KEY_EXCHANGE_DONE:
switch (currentSyncState) { setSyncState(SyncState.DATA_EXCHANGE);
case KEY_EXCHANGE_DONE: break;
setSyncState(SyncState.DATA_EXCHANGE); case DATA_EXCHANGE_DONE:
break; Intent i = new Intent(ExchangeActivity.this, SyncInfoDetailActivity.class);
case DATA_EXCHANGE_DONE: i.putExtra(SyncInfoDetailActivity.EXTRA_SYNC_INFO_UUID, syncInformation.getUuid());
uploadInformation.setCurrentSyncStatus(UploadInformation.SyncStatus.PENDING); startActivity(i);
preferenceManager.addUploadInformation(uploadInformation); finish();
Intent i = new Intent(ExchangeActivity.this, UploadInfoActivity.class); break;
i.putExtra(UploadInfoActivity.EXTRA_UPLOAD_INFO_UUID, uploadInformation.getUuid());
startActivity(i);
finish();
break;
}
} }
}; };
} }

View File

@ -1,8 +1,8 @@
package lu.circl.mispbump.activities; package lu.circl.mispbump.activities;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
@ -11,38 +11,56 @@ import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityOptionsCompat; import androidx.core.app.ActivityOptionsCompat;
import androidx.core.util.Pair;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import java.util.List; import java.util.List;
import lu.circl.mispbump.R; import lu.circl.mispbump.R;
import lu.circl.mispbump.adapters.UploadInfoAdapter; import lu.circl.mispbump.adapters.SyncInfoAdapter;
import lu.circl.mispbump.auxiliary.MispRestClient;
import lu.circl.mispbump.auxiliary.PreferenceManager; import lu.circl.mispbump.auxiliary.PreferenceManager;
import lu.circl.mispbump.interfaces.OnRecyclerItemClickListener; import lu.circl.mispbump.interfaces.OnRecyclerItemClickListener;
import lu.circl.mispbump.models.UploadInformation; import lu.circl.mispbump.models.ExchangeInformation;
import lu.circl.mispbump.models.SyncInformation;
import lu.circl.mispbump.models.restModels.MispServer;
import lu.circl.mispbump.models.restModels.MispUser;
import lu.circl.mispbump.models.restModels.Organisation;
import lu.circl.mispbump.models.restModels.Role;
import lu.circl.mispbump.models.restModels.Server;
import lu.circl.mispbump.models.restModels.User;
public class HomeActivity extends AppCompatActivity { public class HomeActivity extends AppCompatActivity {
public static String EXTRA_UPLOAD_INFO = "uploadInformation"; private List<SyncInformation> syncInformationList;
private List<UploadInformation> uploadInformationList;
private PreferenceManager preferenceManager; private PreferenceManager preferenceManager;
private MispRestClient restClient;
private RecyclerView recyclerView; private RecyclerView recyclerView;
private UploadInfoAdapter uploadInfoAdapter; private SyncInfoAdapter syncInfoAdapter;
private TextView emptyRecyclerView; private TextView emptyRecyclerView;
private SwipeRefreshLayout swipeRefreshLayout;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home); setContentView(R.layout.activity_home);
preferenceManager = PreferenceManager.getInstance(this); preferenceManager = PreferenceManager.getInstance(this);
Pair<String, String> credentials = preferenceManager.getUserCredentials();
restClient = MispRestClient.getInstance(credentials.first, credentials.second);
initViews(); initViews();
initRecyclerView(); initRecyclerView();
checkRequiredInformationAvailable();
} }
@Override @Override
@ -53,26 +71,22 @@ public class HomeActivity extends AppCompatActivity {
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
// if (item.getItemId() == R.id.menu_settings) { if (item.getItemId() == R.id.menu_settings) {
// startActivity(new Intent(HomeActivity.this, PreferenceActivity.class)); startActivity(new Intent(HomeActivity.this, PreferenceActivity.class));
// return true; return true;
// } }
if (item.getItemId() == R.id.menu_profile) { if (item.getItemId() == R.id.menu_profile) {
startActivity(new Intent(HomeActivity.this, ProfileActivity.class)); startActivity(new Intent(HomeActivity.this, ProfileActivity.class));
return true; return true;
} }
// invoke superclass to handle unrecognized item (eg. homeAsUp)
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
Log.d("DEBUG", "onResume()");
refreshRecyclerView(); refreshRecyclerView();
} }
@ -84,46 +98,188 @@ public class HomeActivity extends AppCompatActivity {
setSupportActionBar(myToolbar); setSupportActionBar(myToolbar);
FloatingActionButton syncFab = findViewById(R.id.home_fab); FloatingActionButton syncFab = findViewById(R.id.home_fab);
syncFab.setOnClickListener(new View.OnClickListener() { syncFab.setOnClickListener(v -> startActivity(new Intent(HomeActivity.this, ExchangeActivity.class)));
@Override
public void onClick(View v) { swipeRefreshLayout = findViewById(R.id.swipeRefresh);
startActivity(new Intent(HomeActivity.this, ExchangeActivity.class)); swipeRefreshLayout.setOnRefreshListener(() -> {
} checkUnimportedSyncs();
syncInfoAdapter.setItems(syncInformationList);
}); });
} }
private void initRecyclerView() { private void initRecyclerView() {
recyclerView = findViewById(R.id.recyclerView); recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(HomeActivity.this)); recyclerView.setLayoutManager(new LinearLayoutManager(HomeActivity.this));
uploadInfoAdapter = new UploadInfoAdapter(HomeActivity.this); syncInfoAdapter = new SyncInfoAdapter(HomeActivity.this);
uploadInfoAdapter.setOnRecyclerPositionClickListener(onRecyclerItemClickListener()); syncInfoAdapter.setOnRecyclerPositionClickListener(onRecyclerItemClickListener());
recyclerView.setAdapter(uploadInfoAdapter); recyclerView.setAdapter(syncInfoAdapter);
} }
private void refreshRecyclerView() { private void refreshRecyclerView() {
uploadInformationList = preferenceManager.getUploadInformationList(); syncInformationList = preferenceManager.getSyncInformationList();
if (uploadInformationList.isEmpty()) { if (syncInformationList.isEmpty()) {
emptyRecyclerView.setVisibility(View.VISIBLE); emptyRecyclerView.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE); recyclerView.setVisibility(View.GONE);
} else { } else {
emptyRecyclerView.setVisibility(View.GONE); emptyRecyclerView.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE); recyclerView.setVisibility(View.VISIBLE);
uploadInfoAdapter.setItems(uploadInformationList); syncInfoAdapter.setItems(syncInformationList);
} }
} }
private void checkRequiredInformationAvailable() {
if (preferenceManager.getRoles() == null || preferenceManager.getUserInfo() == null || preferenceManager.getUserOrganisation() == null) {
Pair<String, String> credentials = preferenceManager.getUserCredentials();
MispRestClient client = MispRestClient.getInstance(credentials.first, credentials.second);
// get roles
client.getRoles(new MispRestClient.AllRolesCallback() {
@Override
public void success(Role[] roles) {
preferenceManager.setRoles(roles);
}
@Override
public void failure(String error) {
Snackbar.make(recyclerView, error, Snackbar.LENGTH_LONG).show();
}
});
// get user and organisation
client.getMyUser(new MispRestClient.UserCallback() {
@Override
public void success(User user) {
preferenceManager.setMyUser(user);
client.getOrganisation(user.getOrgId(), new MispRestClient.OrganisationCallback() {
@Override
public void success(Organisation organisation) {
preferenceManager.setMyOrganisation(organisation);
}
@Override
public void failure(String error) {
Snackbar.make(recyclerView, error, Snackbar.LENGTH_LONG).show();
}
});
}
@Override
public void failure(String error) {
Snackbar.make(recyclerView, error, Snackbar.LENGTH_LONG).show();
}
});
}
}
private void checkUnimportedSyncs() {
restClient.getAllServers(new MispRestClient.AllRawServersCallback() {
@Override
public void success(List<MispServer> mispServers) {
if (mispServers.size() < 1) {
return;
}
List<SyncInformation> syncInformationList = preferenceManager.getSyncInformationList();
for (MispServer mispServer : mispServers) {
boolean existsOffline = false;
for (SyncInformation syncInformation : syncInformationList) {
int localServerId = syncInformation.getRemote().getServer().getId();
int remoteServerId = mispServer.getServer().getId();
if (remoteServerId == localServerId) {
existsOffline = true;
break;
}
}
if (!existsOffline) {
// mispServer is not locally available
SyncInformation syncInformation = new SyncInformation();
ExchangeInformation local = new ExchangeInformation();
local.setOrganisation(preferenceManager.getUserOrganisation().toSyncOrganisation());
User syncUser = preferenceManager.getUserInfo().toSyncUser();
syncUser.setAuthkey("Could not be recovered");
syncUser.setPassword("Could not be recovered");
local.setSyncUser(syncUser);
local.setServer(new Server(preferenceManager.getUserCredentials().first));
ExchangeInformation remote = new ExchangeInformation();
remote.setServer(mispServer.getServer());
restClient.getOrganisation(mispServer.getRemoteOrganisation().getId(), new MispRestClient.OrganisationCallback() {
@Override
public void success(Organisation organisation) {
remote.setOrganisation(organisation);
restClient.getAllUsers(new MispRestClient.AllMispUsersCallback() {
@Override
public void success(List<MispUser> users) {
for (MispUser mispUser : users) {
boolean isSyncUserRole = false;
Role[] roles = preferenceManager.getRoles();
for (Role role : roles) {
if (role.getId().equals(mispUser.getRole().getId())) {
isSyncUserRole = role.isSyncUserRole();
break;
}
}
if (mispUser.getOrganisation().getId().equals(organisation.getId()) && isSyncUserRole) {
remote.setSyncUser(mispUser.getUser());
syncInformation.setLocal(local);
syncInformation.setRemote(remote);
preferenceManager.addSyncInformation(syncInformation);
refreshRecyclerView();
}
}
}
@Override
public void failure(String error) {
swipeRefreshLayout.setRefreshing(false);
Snackbar.make(recyclerView, error, Snackbar.LENGTH_LONG).show();
}
});
}
@Override
public void failure(String error) {
swipeRefreshLayout.setRefreshing(false);
Snackbar.make(recyclerView, error, Snackbar.LENGTH_LONG).show();
}
});
}
}
swipeRefreshLayout.setRefreshing(false);
}
@Override
public void failure(String error) {
Snackbar.make(swipeRefreshLayout, error, Snackbar.LENGTH_SHORT).show();
swipeRefreshLayout.setRefreshing(false);
}
});
}
private OnRecyclerItemClickListener<Integer> onRecyclerItemClickListener() { private OnRecyclerItemClickListener<Integer> onRecyclerItemClickListener() {
return new OnRecyclerItemClickListener<Integer>() { return (v, index) -> {
@Override Intent i = new Intent(HomeActivity.this, SyncInfoDetailActivity.class);
public void onClick(View v, Integer index) { i.putExtra(SyncInfoDetailActivity.EXTRA_SYNC_INFO_UUID, syncInformationList.get(index).getUuid());
Intent i = new Intent(HomeActivity.this, UploadInfoActivity.class);
i.putExtra(UploadInfoActivity.EXTRA_UPLOAD_INFO_UUID, uploadInformationList.get(index).getUuid());
ActivityOptionsCompat options = ActivityOptionsCompat.makeClipRevealAnimation(v.findViewById(R.id.rootLayout), (int) v.getX(), (int) v.getY(), v.getWidth(), v.getHeight()); ActivityOptionsCompat options = ActivityOptionsCompat.makeClipRevealAnimation(v.findViewById(R.id.rootLayout), (int) v.getX(), (int) v.getY(), v.getWidth(), v.getHeight());
startActivity(i, options.toBundle()); startActivity(i, options.toBundle());
}
}; };
} }
} }

View File

@ -1,5 +1,6 @@
package lu.circl.mispbump.activities; package lu.circl.mispbump.activities;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
@ -7,10 +8,11 @@ import androidx.appcompat.app.AppCompatActivity;
import lu.circl.mispbump.auxiliary.PreferenceManager; import lu.circl.mispbump.auxiliary.PreferenceManager;
/** /**
* Starts either the login or home activity. * Starts either the login or home activity.
*/ */
public class StartUpActivity extends AppCompatActivity { public class LauncherActivity extends AppCompatActivity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -24,7 +26,7 @@ public class StartUpActivity extends AppCompatActivity {
startActivity(login); startActivity(login);
} }
// closes the activity to prevent going back to this (empty) activity // close activity to prevent going back
finish(); finish();
} }

View File

@ -1,9 +1,9 @@
package lu.circl.mispbump.activities; package lu.circl.mispbump.activities;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
@ -28,6 +28,7 @@ import lu.circl.mispbump.models.restModels.Organisation;
import lu.circl.mispbump.models.restModels.Role; import lu.circl.mispbump.models.restModels.Role;
import lu.circl.mispbump.models.restModels.User; import lu.circl.mispbump.models.restModels.User;
public class LoginActivity extends AppCompatActivity { public class LoginActivity extends AppCompatActivity {
private PreferenceManager preferenceManager; private PreferenceManager preferenceManager;
@ -130,9 +131,9 @@ public class LoginActivity extends AppCompatActivity {
mispRestClient.getMyUser(new MispRestClient.UserCallback() { mispRestClient.getMyUser(new MispRestClient.UserCallback() {
@Override @Override
public void success(final User user) { public void success(final User user) {
preferenceManager.setUserInfo(user); preferenceManager.setMyUser(user);
for (Role role : roles) { for (Role role : roles) {
if (role.getId().equals(user.role_id)) { if (role.getId().equals(user.getRoleId())) {
if (!role.getPermAdmin()) { if (!role.getPermAdmin()) {
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
Snackbar.make(constraintLayout, "No admin is associated with this authkey.", Snackbar.LENGTH_LONG).show(); Snackbar.make(constraintLayout, "No admin is associated with this authkey.", Snackbar.LENGTH_LONG).show();
@ -141,10 +142,10 @@ public class LoginActivity extends AppCompatActivity {
} }
} }
mispRestClient.getOrganisation(user.org_id, new MispRestClient.OrganisationCallback() { mispRestClient.getOrganisation(user.getRoleId(), new MispRestClient.OrganisationCallback() {
@Override @Override
public void success(Organisation organisation) { public void success(Organisation organisation) {
preferenceManager.setUserOrgInfo(organisation); preferenceManager.setMyOrganisation(organisation);
preferenceManager.setUserCredentials(url, authkey); preferenceManager.setUserCredentials(url, authkey);
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
@ -188,10 +189,8 @@ public class LoginActivity extends AppCompatActivity {
}; };
/** /**
* TODO: Check if url is valid.
*
* @param url url to check * @param url url to check
* @return true or false * @return true if valid else false
*/ */
private boolean isValidUrl(String url) { private boolean isValidUrl(String url) {
Uri uri = Uri.parse(url); Uri uri = Uri.parse(url);
@ -204,12 +203,10 @@ public class LoginActivity extends AppCompatActivity {
} }
/** /**
* TODO: Check if automation key is valid.
*
* @param automationKey the key to check * @param automationKey the key to check
* @return true or false * @return true if not empty else false
*/ */
private boolean isValidAutomationKey(String automationKey) { private boolean isValidAutomationKey(String automationKey) {
return !TextUtils.isEmpty(automationKey); return !automationKey.isEmpty();
} }
} }

View File

@ -1,14 +1,20 @@
package lu.circl.mispbump.activities; package lu.circl.mispbump.activities;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle; import android.os.Bundle;
import android.view.View;
import android.widget.Button; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import lu.circl.mispbump.R; import lu.circl.mispbump.R;
import lu.circl.mispbump.auxiliary.PreferenceManager; import lu.circl.mispbump.auxiliary.PreferenceManager;
public class PreferenceActivity extends AppCompatActivity { public class PreferenceActivity extends AppCompatActivity {
private PreferenceManager preferenceManager; private PreferenceManager preferenceManager;
@ -24,12 +30,36 @@ public class PreferenceActivity extends AppCompatActivity {
} }
private void initializeViews() { private void initializeViews() {
Button deleteSyncs = findViewById(R.id.deleteSyncs); Toolbar myToolbar = findViewById(R.id.toolbar);
deleteSyncs.setOnClickListener(new View.OnClickListener() { setSupportActionBar(myToolbar);
@Override
public void onClick(View v) { ActionBar ab = getSupportActionBar();
preferenceManager.clearUploadInformation(); assert ab != null;
} ab.setDisplayHomeAsUpEnabled(true);
});
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
PreferencesFragment preferencesFragment = new PreferencesFragment();
// preferencesFragment.onDeleteAllSyncsListener = preference -> {
// preferenceManager.clearUploadInformation();
// return true;
// };
fragmentTransaction.add(R.id.fragmentContainer, preferencesFragment, PreferencesFragment.class.getSimpleName());
fragmentTransaction.commit();
}
public static class PreferencesFragment extends PreferenceFragmentCompat {
private Preference.OnPreferenceClickListener onDeleteAllSyncsListener;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.preference_screen_main, rootKey);
// Preference deleteAllSyncInfo = findPreference("PREF_DELETE_ALL_SYNCS");
// assert deleteAllSyncInfo != null;
// deleteAllSyncInfo.setOnPreferenceClickListener(onDeleteAllSyncsListener);
}
} }
} }

View File

@ -1,6 +1,6 @@
package lu.circl.mispbump.activities; package lu.circl.mispbump.activities;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.graphics.Shader; import android.graphics.Shader;
import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.AnimatedVectorDrawable;
@ -34,6 +34,7 @@ import lu.circl.mispbump.models.restModels.Role;
import lu.circl.mispbump.models.restModels.User; import lu.circl.mispbump.models.restModels.User;
import lu.circl.mispbump.security.KeyStoreWrapper; import lu.circl.mispbump.security.KeyStoreWrapper;
public class ProfileActivity extends AppCompatActivity { public class ProfileActivity extends AppCompatActivity {
private CoordinatorLayout rootLayout; private CoordinatorLayout rootLayout;
@ -43,6 +44,12 @@ public class ProfileActivity extends AppCompatActivity {
private FloatingActionButton fab; private FloatingActionButton fab;
private AnimatedVectorDrawable fabLoadingDrawable; private AnimatedVectorDrawable fabLoadingDrawable;
private View.OnClickListener onFabClicked = view -> {
fab.setImageDrawable(fabLoadingDrawable);
fabLoadingDrawable.start();
updateProfileInformation();
};
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -52,55 +59,12 @@ public class ProfileActivity extends AppCompatActivity {
Pair<String, String> credentials = preferenceManager.getUserCredentials(); Pair<String, String> credentials = preferenceManager.getUserCredentials();
mispRestClient = MispRestClient.getInstance(credentials.first, credentials.second); mispRestClient = MispRestClient.getInstance(credentials.first, credentials.second);
init(); initToolbar();
initViews();
populateInformationViews(); populateInformationViews();
} }
private void init() {
rootLayout = findViewById(R.id.rootLayout);
ImageView headerBg = findViewById(R.id.headerBg);
headerBg.setImageDrawable(new TileDrawable(getRandomHeader(), Shader.TileMode.REPEAT));
// populate Toolbar (Actionbar)
Toolbar myToolbar = findViewById(R.id.toolbar);
setSupportActionBar(myToolbar);
ActionBar ab = getSupportActionBar();
if (ab != null) {
ab.setDisplayHomeAsUpEnabled(true);
ab.setDisplayShowTitleEnabled(true);
}
fab = findViewById(R.id.fab);
fab.setOnClickListener(onFabClicked());
fabLoadingDrawable = (AnimatedVectorDrawable) getDrawable(R.drawable.animated_sync);
}
private void populateInformationViews() {
Organisation organisation = preferenceManager.getUserOrganisation();
TextView name = findViewById(R.id.orgName);
name.setText(organisation.getName());
final MaterialPreferenceText uuid = findViewById(R.id.uuid);
uuid.setSubtitle(organisation.getUuid());
MaterialPreferenceText nationality = findViewById(R.id.nationality);
nationality.setSubtitle(organisation.getNationality());
MaterialPreferenceText sector = findViewById(R.id.sector);
if (organisation.getSector() == null) {
sector.setVisibility(View.GONE);
} else {
sector.setSubtitle(organisation.getSector());
}
MaterialPreferenceText description = findViewById(R.id.description);
description.setSubtitle(organisation.getDescription());
}
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_profile, menu); getMenuInflater().inflate(R.menu.menu_profile, menu);
@ -117,23 +81,51 @@ public class ProfileActivity extends AppCompatActivity {
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
private View.OnClickListener onFabClicked() {
return new View.OnClickListener() { private void initToolbar() {
@Override Toolbar myToolbar = findViewById(R.id.toolbar);
public void onClick(View v) { setSupportActionBar(myToolbar);
fab.setImageDrawable(fabLoadingDrawable);
fabLoadingDrawable.start(); ActionBar ab = getSupportActionBar();
updateProfile(); if (ab != null) {
} ab.setDisplayHomeAsUpEnabled(true);
}; ab.setDisplayShowTitleEnabled(true);
}
} }
private Drawable getRandomHeader() { private void initViews() {
int[] ids = {R.drawable.ic_bank_note, R.drawable.ic_polka_dots, R.drawable.ic_wiggle, R.drawable.ic_circuit_board}; rootLayout = findViewById(R.id.rootLayout);
return getDrawable(ids[new Random().nextInt(ids.length)]);
ImageView headerBg = findViewById(R.id.headerBg);
headerBg.setImageDrawable(new TileDrawable(getRandomHeader(), Shader.TileMode.REPEAT));
fab = findViewById(R.id.fab);
fab.setOnClickListener(onFabClicked);
fabLoadingDrawable = (AnimatedVectorDrawable) getDrawable(R.drawable.animated_sync);
} }
public void updateProfile() { private void populateInformationViews() {
Organisation organisation = preferenceManager.getUserOrganisation();
TextView name = findViewById(R.id.orgName);
name.setText(organisation.getName());
final MaterialPreferenceText uuid = findViewById(R.id.uuid);
uuid.setSubtitle(organisation.getUuid());
MaterialPreferenceText nationality = findViewById(R.id.nationality);
nationality.setSubtitle(organisation.getNationality());
MaterialPreferenceText sector = findViewById(R.id.sector);
sector.setSubtitle(organisation.getSector());
MaterialPreferenceText description = findViewById(R.id.description);
description.setSubtitle(organisation.getDescription());
}
public void updateProfileInformation() {
mispRestClient.getRoles(new MispRestClient.AllRolesCallback() { mispRestClient.getRoles(new MispRestClient.AllRolesCallback() {
@Override @Override
public void success(Role[] roles) { public void success(Role[] roles) {
@ -149,12 +141,12 @@ public class ProfileActivity extends AppCompatActivity {
mispRestClient.getMyUser(new MispRestClient.UserCallback() { mispRestClient.getMyUser(new MispRestClient.UserCallback() {
@Override @Override
public void success(final User user) { public void success(final User user) {
preferenceManager.setUserInfo(user); preferenceManager.setMyUser(user);
mispRestClient.getOrganisation(user.org_id, new MispRestClient.OrganisationCallback() { mispRestClient.getOrganisation(user.getRoleId(), new MispRestClient.OrganisationCallback() {
@Override @Override
public void success(Organisation organisation) { public void success(Organisation organisation) {
fabLoadingDrawable.stop(); fabLoadingDrawable.stop();
preferenceManager.setUserOrgInfo(organisation); preferenceManager.setMyOrganisation(organisation);
Snackbar.make(rootLayout, "Successfully update profile", Snackbar.LENGTH_SHORT).show(); Snackbar.make(rootLayout, "Successfully update profile", Snackbar.LENGTH_SHORT).show();
} }
@ -179,25 +171,23 @@ public class ProfileActivity extends AppCompatActivity {
builder.setTitle("Clear all saved data and logout"); builder.setTitle("Clear all saved data and logout");
builder.setMessage("Do you really want to delete all data and logout?"); builder.setMessage("Do you really want to delete all data and logout?");
builder.setNegativeButton("Discard", new DialogInterface.OnClickListener() { builder.setNegativeButton("Discard", (dialog, which) -> dialog.cancel());
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
builder.setPositiveButton("Delete & Logout", new DialogInterface.OnClickListener() { builder.setPositiveButton("Delete & Logout", (dialog, which) -> {
@Override preferenceManager.clearAllData();
public void onClick(DialogInterface dialog, int which) { KeyStoreWrapper.deleteAllStoredKeys();
preferenceManager.clearAllData();
KeyStoreWrapper.deleteAllStoredKeys();
Intent login = new Intent(getApplicationContext(), LoginActivity.class); Intent login = new Intent(getApplicationContext(), LoginActivity.class);
startActivity(login); startActivity(login);
finish(); finish();
}
}); });
builder.create().show(); builder.create().show();
} }
private Drawable getRandomHeader() {
int[] ids = {R.drawable.ic_bank_note, R.drawable.ic_polka_dots, R.drawable.ic_wiggle, R.drawable.ic_circuit_board};
return getDrawable(ids[new Random().nextInt(ids.length)]);
}
} }

View File

@ -0,0 +1,277 @@
package lu.circl.mispbump.activities;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Intent;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.os.Bundle;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
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.MaterialPreferenceSwitch;
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;
private boolean dataLocallyChanged;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sync_info_detail_v2);
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();
if (dataLocallyChanged) {
syncInformation.setSyncedWithRemote(false);
}
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);
TextView uploadText = findViewById(R.id.fab_upload_text);
TextView downloadText = findViewById(R.id.fab_download_text);
View menuBackground = findViewById(R.id.menu_background);
uploadLayout.setVisibility(View.GONE);
downloadLayout.setVisibility(View.GONE);
int animationSpeed = 200;
ValueAnimator openAnimation = ValueAnimator.ofFloat(0f, 1f);
openAnimation.setDuration(animationSpeed);
openAnimation.setInterpolator(new DecelerateInterpolator());
openAnimation.addUpdateListener(updateAnimation -> {
float x = (float) updateAnimation.getAnimatedValue();
fabUpload.setAlpha(x);
fabUpload.setTranslationY((1 - x) * 50);
uploadText.setAlpha(x);
uploadText.setTranslationX((1 - x) * -200);
fabDownload.setAlpha(x);
fabDownload.setTranslationY((1 - x) * 50);
downloadText.setAlpha(x);
downloadText.setTranslationX((1 - x) * -200);
menuBackground.setAlpha(x * 0.9f);
});
openAnimation.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
uploadLayout.setVisibility(View.VISIBLE);
downloadLayout.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationEnd(Animator animator) {
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
ValueAnimator closeAnimation = ValueAnimator.ofFloat(1f, 0f);
closeAnimation.setDuration(animationSpeed);
closeAnimation.setInterpolator(new DecelerateInterpolator());
closeAnimation.addUpdateListener(updateAnimation -> {
float x = (float) updateAnimation.getAnimatedValue();
fabUpload.setAlpha(x);
fabUpload.setTranslationY((1 - x) * 50);
uploadText.setAlpha(x);
uploadText.setTranslationX((1 - x) * -200);
fabDownload.setAlpha(x);
fabDownload.setTranslationY((1 - x) * 50);
downloadText.setAlpha(x);
downloadText.setTranslationX((1 - x) * -200);
menuBackground.setAlpha(x * 0.9f);
});
closeAnimation.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
uploadLayout.setVisibility(View.VISIBLE);
downloadLayout.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationEnd(Animator animator) {
uploadLayout.setVisibility(View.GONE);
downloadLayout.setVisibility(View.GONE);
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
AnimatedVectorDrawable open = (AnimatedVectorDrawable) getDrawable(R.drawable.animated_arrow_cloud_down);
AnimatedVectorDrawable close = (AnimatedVectorDrawable) getDrawable(R.drawable.animated_arrow_down_cloud);
View.OnClickListener expandCollapseClick = view -> {
if (fabMenuExpanded) {
menuBackground.setClickable(false);
fab.setImageDrawable(close);
close.start();
closeAnimation.start();
fabMenuExpanded = false;
} else {
menuBackground.setClickable(true);
fab.setImageDrawable(open);
open.start();
openAnimation.start();
fabMenuExpanded = true;
}
};
menuBackground.setOnClickListener(expandCollapseClick);
menuBackground.setClickable(false);
fab.setOnClickListener(expandCollapseClick);
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 -> {
// TODO download content from MISP instance
Snackbar.make(view, "Not implemented yet", Snackbar.LENGTH_LONG).show();
});
}
private void populateContent() {
// information
MaterialPreferenceText name = findViewById(R.id.name);
name.setSubtitle(syncInformation.getRemote().getOrganisation().getName());
MaterialPreferenceText uuid = findViewById(R.id.uuid);
uuid.setSubtitle(syncInformation.getRemote().getOrganisation().getUuid());
MaterialPreferenceText sector = findViewById(R.id.sector);
sector.setSubtitle(syncInformation.getRemote().getOrganisation().getSector());
MaterialPreferenceText description = findViewById(R.id.description);
description.setSubtitle(syncInformation.getRemote().getOrganisation().getDescription());
// settings
MaterialPreferenceSwitch allowSelfSigned = findViewById(R.id.switch_allow_self_signed);
allowSelfSigned.setChecked(syncInformation.getRemote().getServer().getSelfSigned());
allowSelfSigned.setOnCheckedChangeListener((cb, b) -> {
syncInformation.getRemote().getServer().setSelfSigned(b);
dataLocallyChanged = true;
});
MaterialPreferenceSwitch allowPush = findViewById(R.id.switch_allow_push);
allowPush.setChecked(syncInformation.getRemote().getServer().getPush());
allowPush.setOnCheckedChangeListener((cb, b) -> {
syncInformation.getRemote().getServer().setPush(b);
dataLocallyChanged = true;
});
MaterialPreferenceSwitch allowPull = findViewById(R.id.switch_allow_pull);
allowPull.setChecked(syncInformation.getRemote().getServer().getPull());
allowPull.setOnCheckedChangeListener((cb, b) -> {
syncInformation.getRemote().getServer().setPull(b);
dataLocallyChanged = true;
});
MaterialPreferenceSwitch allowCache = findViewById(R.id.switch_allow_cache);
allowCache.setChecked(syncInformation.getRemote().getServer().getCachingEnabled());
allowCache.setOnCheckedChangeListener((cb, b) -> {
syncInformation.getRemote().getServer().setCachingEnabled(b);
dataLocallyChanged = true;
});
// 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());
}
}

View File

@ -1,38 +1,95 @@
package lu.circl.mispbump.activities; package lu.circl.mispbump.activities;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.view.MenuItem; 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.ActionBar;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.core.util.Pair; import androidx.core.util.Pair;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import java.util.UUID; import java.util.UUID;
import lu.circl.mispbump.R; import lu.circl.mispbump.R;
import lu.circl.mispbump.auxiliary.MispRestClient; import lu.circl.mispbump.auxiliary.MispRestClient;
import lu.circl.mispbump.auxiliary.PreferenceManager; import lu.circl.mispbump.auxiliary.PreferenceManager;
import lu.circl.mispbump.customViews.UploadAction; import lu.circl.mispbump.customViews.ProgressActionView;
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.Organisation;
import lu.circl.mispbump.models.restModels.Role;
import lu.circl.mispbump.models.restModels.Server; import lu.circl.mispbump.models.restModels.Server;
import lu.circl.mispbump.models.restModels.User; import lu.circl.mispbump.models.restModels.User;
public class UploadActivity extends AppCompatActivity { 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 PreferenceManager preferenceManager; 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);
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() { private MispRestClient.AvailableCallback availableCallback = new MispRestClient.AvailableCallback() {
@Override @Override
@ -90,187 +147,70 @@ 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) { private User generateSyncUser(Organisation organisation) {
User syncUser = new User(); User syncUser = syncInformation.getRemote().getSyncUser();
syncUser.org_id = organisation.getId();
syncUser.role_id = User.ROLE_SYNC_USER; syncUser.setOrgId(organisation.getId());
syncUser.email = uploadInformation.getRemote().syncUserEmail; syncUser.setTermsAccepted(true);
syncUser.password = uploadInformation.getRemote().syncUserPassword;
syncUser.authkey = uploadInformation.getRemote().syncUserAuthkey; Role[] roles = preferenceManager.getRoles();
syncUser.termsaccepted = true; for (Role role : roles) {
if (role.isSyncUserRole()) {
syncUser.setRoleId(role.getId());
}
}
return syncUser; return syncUser;
} }
private Server generateSyncServer() { private Server generateSyncServer() {
Server server = new Server(); Server server = syncInformation.getRemote().getServer();
server.name = uploadInformation.getRemote().organisation.getName() + "'s Sync Server"; server.setName(syncInformation.getRemote().getOrganisation().getName() + "'s Sync Server");
server.url = uploadInformation.getRemote().baseUrl; server.setRemoteOrgId(syncInformation.getRemote().getOrganisation().getId());
server.remote_org_id = uploadInformation.getRemote().organisation.getId(); server.setAuthkey(syncInformation.getRemote().getSyncUser().getAuthkey());
server.authkey = uploadInformation.getLocal().syncUserAuthkey; server.setPull(syncInformation.getRemote().getServer().getPull());
server.pull = uploadInformation.isPull(); server.setPush(syncInformation.getRemote().getServer().getPush());
server.push = uploadInformation.isPush(); server.setCachingEnabled(syncInformation.getRemote().getServer().getCachingEnabled());
server.caching_enabled = uploadInformation.isCached(); server.setSelfSigned(syncInformation.getRemote().getServer().getCachingEnabled());
server.self_signed = uploadInformation.isAllowSelfSigned();
return server; return server;
} }
/**
* Start upload to misp instance.
*/
private void startUpload() { private void startUpload() {
availableAction.setCurrentUploadState(UploadAction.UploadState.LOADING); availableAction.start();
restClient.isAvailable(availableCallback); mispRest.isAvailable(availableCallback);
} }
private void mispAvailable(boolean available, String error) { private void mispAvailable(boolean available, String error) {
if (available) { if (available) {
setUploadActionState(availableAction, UploadAction.UploadState.DONE, null); availableAction.done();
restClient.addOrganisation(uploadInformation.getRemote().organisation, organisationCallback); organisationAction.start();
mispRest.addOrganisation(syncInformation.getRemote().getOrganisation(), organisationCallback);
} else { } else {
setUploadActionState(availableAction, UploadAction.UploadState.ERROR, error); availableAction.error(error);
} }
} }
private void organisationAdded(Organisation organisation) { private void organisationAdded(Organisation organisation) {
if (organisation != null) { if (organisation != null) {
setUploadActionState(orgAction, UploadAction.UploadState.DONE, null); organisationAction.done();
uploadInformation.getRemote().organisation.setId(organisation.getId()); userAction.start();
restClient.addUser(generateSyncUser(organisation), userCallback);
syncInformation.getRemote().getOrganisation().setId(organisation.getId());
mispRest.addUser(generateSyncUser(organisation), userCallback);
} else { } else {
// search by UUID because the error does not give the actual ID mispRest.getOrganisation(syncInformation.getRemote().getOrganisation().getUuid(), new MispRestClient.OrganisationCallback() {
restClient.getOrganisation(uploadInformation.getRemote().organisation.getUuid(), new MispRestClient.OrganisationCallback() {
@Override @Override
public void success(Organisation organisation) { public void success(Organisation organisation) {
organisationAdded(organisation); organisationAdded(organisation);
organisationAction.done("Organisation already on MISP instance");
} }
@Override @Override
public void failure(String error) { public void failure(String error) {
setUploadActionState(orgAction, UploadAction.UploadState.ERROR, error); organisationAction.error(error);
} }
}); });
} }
@ -278,18 +218,21 @@ public class UploadActivity extends AppCompatActivity {
private void userAdded(User user) { private void userAdded(User user) {
if (user != null) { if (user != null) {
setUploadActionState(userAction, UploadAction.UploadState.DONE, null); userAction.done();
restClient.getAllServers(allServersCallback); serverAction.start();
mispRest.getAllServers(allServersCallback);
} else { } else {
restClient.getUser(uploadInformation.getRemote().syncUserEmail, new MispRestClient.UserCallback() { mispRest.getUser(syncInformation.getLocal().getSyncUser().getEmail(), new MispRestClient.UserCallback() {
@Override @Override
public void success(User user) { public void success(User user) {
userAdded(user); userAdded(user);
userAction.done("User already on MISP instance");
} }
@Override @Override
public void failure(String error) { public void failure(String error) {
setUploadActionState(userAction, UploadAction.UploadState.ERROR, error); userAction.error(error);
} }
}); });
} }
@ -300,35 +243,25 @@ public class UploadActivity extends AppCompatActivity {
Server serverToUpload = generateSyncServer(); Server serverToUpload = generateSyncServer();
for (Server server : servers) { for (Server server : servers) {
if (server.remote_org_id.equals(serverToUpload.remote_org_id)) { if (server.getRemoteOrgId().equals(serverToUpload.getRemoteOrgId())) {
// server already exists: override id to update instead // server already exists: override id to update instead
serverToUpload.id = server.id; serverToUpload.setId(server.getId());
break; break;
} }
} }
restClient.addServer(serverToUpload, serverCallback); mispRest.addServer(serverToUpload, serverCallback);
} else { } 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) { private void serverAdded(Server server) {
if (server != null) { if (server != null) {
setUploadActionState(serverAction, UploadAction.UploadState.DONE, null); serverAction.done();
uploadInformation.setCurrentSyncStatus(UploadInformation.SyncStatus.COMPLETE); preferenceManager.addSyncInformation(syncInformation);
saveCurrentState();
fab.setImageResource(R.drawable.ic_check);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
fab.show();
} else { } else {
setUploadActionState(serverAction, UploadAction.UploadState.ERROR, "Could not add server"); serverAction.error("Could not create Sync Server");
} }
} }
} }

View File

@ -1,266 +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;
}
}
}

View File

@ -0,0 +1,120 @@
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.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 Context context;
private List<SyncInformation> items;
private OnRecyclerItemClickListener<Integer> onRecyclerPositionClickListener;
public SyncInfoAdapter(Context context) {
this.context = context;
}
@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.getRemote().getOrganisation().getName());
if (item.isSyncedWithRemote()) {
ImageViewCompat.setImageTintList(holder.syncStatus, ColorStateList.valueOf(context.getColor(R.color.status_green)));
holder.syncStatus.setImageResource(R.drawable.ic_check_outline);
} else {
ImageViewCompat.setImageTintList(holder.syncStatus, ColorStateList.valueOf(context.getColor(R.color.status_amber)));
holder.syncStatus.setImageResource(R.drawable.ic_error_outline);
}
// 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() {
if (items == null) {
return 0;
}
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);
}
}
}

View File

@ -1,118 +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<UploadInformation> onRecyclerItemClickListener;
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(new View.OnClickListener() {
@Override
public void onClick(View view) {
onRecyclerItemClickListener.onClick(view, item);
}
});
holder.rootView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onRecyclerPositionClickListener.onClick(view, position);
}
});
}
@Override
public int getItemCount() {
return items.size();
}
public void setItems(List<UploadInformation> items) {
this.items = items;
notifyDataSetChanged();
}
// callbacks
public void setOnRecyclerItemClickListener(OnRecyclerItemClickListener<UploadInformation> onRecyclerItemClickListener) {
this.onRecyclerItemClickListener = onRecyclerItemClickListener;
}
public void setOnRecyclerPositionClickListener(OnRecyclerItemClickListener<Integer> onRecyclerPositionClickListener) {
this.onRecyclerPositionClickListener = onRecyclerPositionClickListener;
}
// viewHolder
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);
}
}
}

View File

@ -1,9 +1,9 @@
package lu.circl.mispbump.auxiliary; package lu.circl.mispbump.auxiliary;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.util.Log;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
@ -13,6 +13,7 @@ import lu.circl.mispbump.R;
import lu.circl.mispbump.models.SyncInformation; import lu.circl.mispbump.models.SyncInformation;
import lu.circl.mispbump.security.DiffieHellman; import lu.circl.mispbump.security.DiffieHellman;
/** /**
* Creates and show dialogs. * Creates and show dialogs.
* Automatically takes care of using the UI Thread. * Automatically takes care of using the UI Thread.
@ -26,11 +27,11 @@ public class DialogManager {
// this dialog needs definite user feedback // this dialog needs definite user feedback
adb.setCancelable(false); adb.setCancelable(false);
if (oldSync.organisation.getName().equals(newSync.organisation.getName())) { // if (oldSync.organisation.getName().equals(newSync.organisation.getName())) {
adb.setTitle("Already Synced with " + oldSync.organisation.getName()); // adb.setTitle("Already Synced with " + oldSync.organisation.getName());
} else { // } else {
adb.setTitle("Already Synced with " + oldSync.organisation.getName() + "(Now:" + newSync.organisation.getName() + ")"); // adb.setTitle("Already Synced with " + oldSync.organisation.getName() + "(Now:" + newSync.organisation.getName() + ")");
} // }
adb.setMessage(""); adb.setMessage("");
@ -134,41 +135,30 @@ public class DialogManager {
* Dialog to display a received public key. * Dialog to display a received public key.
* *
* @param syncInformation {@link SyncInformation} * @param syncInformation {@link SyncInformation}
* @param context needed to build and show the dialog * @param context needed to build and show the dialog
* @param callback {@link IDialogFeedback} * @param callback {@link IDialogFeedback}
*/ */
public static void syncInformationDialog(SyncInformation syncInformation, Context context, final IDialogFeedback callback) { public static void syncInformationDialog(SyncInformation syncInformation, Context context, final IDialogFeedback callback) {
final AlertDialog.Builder adb = new AlertDialog.Builder(context); final AlertDialog.Builder adb = new AlertDialog.Builder(context);
adb.setTitle("Sync information received"); adb.setTitle("Sync information received");
adb.setMessage(syncInformation.organisation.getName()); adb.setMessage(syncInformation.getRemote().getOrganisation().getName());
adb.setPositiveButton("Accept", new DialogInterface.OnClickListener() { adb.setPositiveButton("Accept", (dialog, which) -> {
@Override if (callback != null) {
public void onClick(DialogInterface dialog, int which) { callback.positive();
if (callback != null) {
callback.positive();
}
} }
}); });
adb.setNegativeButton("Reject", new DialogInterface.OnClickListener() { adb.setNegativeButton("Reject", (dialog, which) -> {
@Override if (callback != null) {
public void onClick(DialogInterface dialog, int which) { callback.negative();
if (callback != null) {
callback.negative();
}
} }
}); });
Activity act = (Activity) context; Activity act = (Activity) context;
act.runOnUiThread(new Runnable() { act.runOnUiThread(() -> adb.create().show());
@Override
public void run() {
adb.create().show();
}
});
} }
/** /**
@ -182,31 +172,20 @@ public class DialogManager {
adb.setTitle("Continue?"); adb.setTitle("Continue?");
adb.setMessage("Only continue if your partner already scanned this QR code"); adb.setMessage("Only continue if your partner already scanned this QR code");
adb.setPositiveButton("Continue", new DialogInterface.OnClickListener() { adb.setPositiveButton("Continue", (dialog, which) -> {
@Override if (callback != null) {
public void onClick(DialogInterface dialog, int which) { callback.positive();
if (callback != null) {
callback.positive();
}
} }
}); });
adb.setNegativeButton("Show QR code again", new DialogInterface.OnClickListener() { adb.setNegativeButton("Show QR code again", (dialog, which) -> {
@Override if (callback != null) {
public void onClick(DialogInterface dialog, int which) { callback.negative();
if (callback != null) {
callback.negative();
}
} }
}); });
Activity act = (Activity) context; Activity act = (Activity) context;
act.runOnUiThread(new Runnable() { act.runOnUiThread(() -> adb.create().show());
@Override
public void run() {
adb.create().show();
}
});
} }
/** /**
@ -216,23 +195,12 @@ public class DialogManager {
*/ */
public static void loginHelpDialog(Context context) { public static void loginHelpDialog(Context context) {
final AlertDialog.Builder adb = new AlertDialog.Builder(context); final AlertDialog.Builder adb = new AlertDialog.Builder(context);
// adb.setTitle(R.string.app_name);
adb.setMessage(R.string.login_help_text); adb.setMessage(R.string.login_help_text);
adb.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { adb.setPositiveButton(R.string.ok, (dialog, which) -> dialog.dismiss());
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
Activity act = (Activity) context; Activity act = (Activity) context;
act.runOnUiThread(new Runnable() { act.runOnUiThread(() -> adb.create().show());
@Override
public void run() {
adb.create().show();
}
});
} }
public static void instanceNotAvailableDialog(Context context, final IDialogFeedback callback) { public static void instanceNotAvailableDialog(Context context, final IDialogFeedback callback) {
@ -240,31 +208,20 @@ public class DialogManager {
adb.setTitle("MISP not available"); adb.setTitle("MISP not available");
adb.setMessage("Your MISP instance is not available. Would you like to save?"); adb.setMessage("Your MISP instance is not available. Would you like to save?");
adb.setPositiveButton("Retry now", new DialogInterface.OnClickListener() { adb.setPositiveButton("Retry now", (dialog, which) -> {
@Override if (callback != null) {
public void onClick(DialogInterface dialog, int which) { callback.positive();
if (callback != null) {
callback.positive();
}
} }
}); });
adb.setNegativeButton("Save & retry later", new DialogInterface.OnClickListener() { adb.setNegativeButton("Save & retry later", (dialog, which) -> {
@Override if (callback != null) {
public void onClick(DialogInterface dialog, int which) { callback.negative();
if (callback != null) {
callback.negative();
}
} }
}); });
Activity act = (Activity) context; Activity act = (Activity) context;
act.runOnUiThread(new Runnable() { act.runOnUiThread(() -> adb.create().show());
@Override
public void run() {
adb.create().show();
}
});
} }
public static void deleteSyncInformationDialog(Context context, final IDialogFeedback callback) { public static void deleteSyncInformationDialog(Context context, final IDialogFeedback callback) {
@ -272,31 +229,20 @@ public class DialogManager {
adb.setTitle("Delete Sync Information?"); adb.setTitle("Delete Sync Information?");
adb.setMessage("This sync information will be deleted permanently"); adb.setMessage("This sync information will be deleted permanently");
adb.setPositiveButton("Delete", new DialogInterface.OnClickListener() { adb.setPositiveButton("Delete", (dialog, which) -> {
@Override if (callback != null) {
public void onClick(DialogInterface dialog, int which) { callback.positive();
if (callback != null) {
callback.positive();
}
} }
}); });
adb.setNegativeButton("Discard", new DialogInterface.OnClickListener() { adb.setNegativeButton("Discard", (dialog, which) -> {
@Override if (callback != null) {
public void onClick(DialogInterface dialog, int which) { callback.negative();
if (callback != null) {
callback.negative();
}
} }
}); });
Activity act = (Activity) context; Activity act = (Activity) context;
act.runOnUiThread(new Runnable() { act.runOnUiThread(() -> adb.create().show());
@Override
public void run() {
adb.create().show();
}
});
} }
/** /**
@ -304,8 +250,7 @@ public class DialogManager {
*/ */
public interface IDialogFeedback { public interface IDialogFeedback {
void positive(); void positive();
void negative(); void negative();
} }
} }

View File

@ -1,23 +1,21 @@
package lu.circl.mispbump.auxiliary; package lu.circl.mispbump.auxiliary;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import java.io.IOException;
import java.net.NoRouteToHostException; import java.net.NoRouteToHostException;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.util.List; import java.util.List;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager; 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.MispOrganisation;
import lu.circl.mispbump.models.restModels.MispRole; import lu.circl.mispbump.models.restModels.MispRole;
import lu.circl.mispbump.models.restModels.MispServer; import lu.circl.mispbump.models.restModels.MispServer;
@ -27,7 +25,6 @@ import lu.circl.mispbump.models.restModels.Role;
import lu.circl.mispbump.models.restModels.Server; import lu.circl.mispbump.models.restModels.Server;
import lu.circl.mispbump.models.restModels.User; import lu.circl.mispbump.models.restModels.User;
import lu.circl.mispbump.models.restModels.Version; import lu.circl.mispbump.models.restModels.Version;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.logging.HttpLoggingInterceptor; import okhttp3.logging.HttpLoggingInterceptor;
@ -37,6 +34,7 @@ import retrofit2.Response;
import retrofit2.Retrofit; import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory; import retrofit2.converter.gson.GsonConverterFactory;
/** /**
* Implementation of the RetroFit2 Misp client. * Implementation of the RetroFit2 Misp client.
* In order to conveniently use this api some wrapper interfaces are implemented to return the requested API endpoint as java object. * In order to conveniently use this api some wrapper interfaces are implemented to return the requested API endpoint as java object.
@ -45,7 +43,7 @@ public class MispRestClient {
private static MispRestClient instance; private static MispRestClient instance;
private MispRestInterface mispRestInterface; private MispService mispService;
public static MispRestClient getInstance(String url, String authkey) { public static MispRestClient getInstance(String url, String authkey) {
if (instance == null) { if (instance == null) {
@ -65,13 +63,18 @@ public class MispRestClient {
.client(getCustomClient(true, true, authkey)) .client(getCustomClient(true, true, authkey))
.build(); .build();
mispRestInterface = retrofit.create(MispRestInterface.class); mispService = retrofit.create(MispService.class);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
public MispService getService() {
return mispService;
}
/** /**
* @param unsafe whether to accept all certificates or only trusted ones * @param unsafe whether to accept all certificates or only trusted ones
* @param logging whether to log Retrofit calls (for debugging) * @param logging whether to log Retrofit calls (for debugging)
@ -88,12 +91,14 @@ public class MispRestClient {
new X509TrustManager() { new X509TrustManager() {
@SuppressLint("TrustAllX509TrustManager") @SuppressLint("TrustAllX509TrustManager")
@Override @Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException { public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {
// nothing to do
} }
@SuppressLint("TrustAllX509TrustManager") @SuppressLint("TrustAllX509TrustManager")
@Override @Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException { public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {
// nothing to do
} }
@Override @Override
@ -111,12 +116,7 @@ public class MispRestClient {
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]); builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]);
builder.hostnameVerifier(new HostnameVerifier() { builder.hostnameVerifier((hostname, session) -> true);
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
} }
if (logging) { if (logging) {
@ -125,16 +125,13 @@ public class MispRestClient {
builder.addInterceptor(interceptor); builder.addInterceptor(interceptor);
} }
// create authorization interceptor // create interceptor
builder.addInterceptor(new Interceptor() { builder.addInterceptor(chain -> {
@Override Request.Builder ongoing = chain.request().newBuilder();
public okhttp3.Response intercept(Chain chain) throws IOException { ongoing.addHeader("Accept", "application/json");
Request.Builder ongoing = chain.request().newBuilder(); ongoing.addHeader("Content-Type", "application/json");
ongoing.addHeader("Accept", "application/json"); ongoing.addHeader("Authorization", authkey);
ongoing.addHeader("Content-Type", "application/json"); return chain.proceed(ongoing.build());
ongoing.addHeader("Authorization", authkey);
return chain.proceed(ongoing.build());
}
}); });
return builder.build(); return builder.build();
@ -151,7 +148,7 @@ public class MispRestClient {
* @param callback {@link AvailableCallback} * @param callback {@link AvailableCallback}
*/ */
public void isAvailable(final AvailableCallback callback) { public void isAvailable(final AvailableCallback callback) {
Call<Version> call = mispRestInterface.pyMispVersion(); Call<Version> call = mispService.pyMispVersion();
call.enqueue(new Callback<Version>() { call.enqueue(new Callback<Version>() {
@Override @Override
public void onResponse(@NonNull Call<Version> call, @NonNull Response<Version> response) { public void onResponse(@NonNull Call<Version> call, @NonNull Response<Version> response) {
@ -175,10 +172,10 @@ public class MispRestClient {
} }
public void getRoles(final AllRolesCallback callback) { public void getRoles(final AllRolesCallback callback) {
Call<List<MispRole>> call = mispRestInterface.getRoles(); Call<List<MispRole>> call = mispService.getRoles();
call.enqueue(new Callback<List<MispRole>>() { call.enqueue(new Callback<List<MispRole>>() {
@Override @Override
public void onResponse(Call<List<MispRole>> call, Response<List<MispRole>> response) { public void onResponse(@NonNull Call<List<MispRole>> call, @NonNull Response<List<MispRole>> response) {
if (!response.isSuccessful()) { if (!response.isSuccessful()) {
callback.failure(extractError(response)); callback.failure(extractError(response));
@ -198,7 +195,7 @@ public class MispRestClient {
} }
@Override @Override
public void onFailure(Call<List<MispRole>> call, Throwable t) { public void onFailure(@NonNull Call<List<MispRole>> call, @NonNull Throwable t) {
callback.failure(extractError(t)); callback.failure(extractError(t));
} }
}); });
@ -211,7 +208,7 @@ public class MispRestClient {
* @param callback {@link UserCallback} wrapper to return user directly * @param callback {@link UserCallback} wrapper to return user directly
*/ */
public void getMyUser(final UserCallback callback) { public void getMyUser(final UserCallback callback) {
Call<MispUser> call = mispRestInterface.getMyUserInformation(); Call<MispUser> call = mispService.getMyUserInformation();
call.enqueue(new Callback<MispUser>() { call.enqueue(new Callback<MispUser>() {
@Override @Override
@ -220,7 +217,7 @@ public class MispRestClient {
callback.failure(extractError(response)); callback.failure(extractError(response));
} else { } else {
if (response.body() != null) { if (response.body() != null) {
callback.success(response.body().user); callback.success(response.body().getUser());
} else { } else {
callback.failure("response body was null"); callback.failure("response body was null");
} }
@ -241,9 +238,8 @@ public class MispRestClient {
* @param userId user identifier * @param userId user identifier
* @param callback {@link UserCallback} wrapper to return user directly * @param callback {@link UserCallback} wrapper to return user directly
*/ */
public void getUser(int userId, final UserCallback callback) { 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>() { call.enqueue(new Callback<MispUser>() {
@Override @Override
@ -252,7 +248,7 @@ public class MispRestClient {
callback.failure(extractError(response)); callback.failure(extractError(response));
} else { } else {
if (response.body() != null) { if (response.body() != null) {
callback.success(response.body().user); callback.success(response.body().getUser());
} else { } else {
callback.failure("response body was null"); callback.failure("response body was null");
} }
@ -272,7 +268,7 @@ public class MispRestClient {
@Override @Override
public void success(User[] users) { public void success(User[] users) {
for (User user : users) { for (User user : users) {
if (user.email.equals(emailAddress)) { if (user.getEmail().equals(emailAddress)) {
callback.success(user); callback.success(user);
return; return;
} }
@ -288,8 +284,29 @@ public class MispRestClient {
}); });
} }
public void getAllUsers(final AllMispUsersCallback callback) {
Call<List<MispUser>> call = mispService.getAllUsers();
call.enqueue(new Callback<List<MispUser>>() {
@Override
public void onResponse(@NonNull Call<List<MispUser>> call, @NonNull Response<List<MispUser>> response) {
if (!response.isSuccessful()) {
callback.failure("Failed onResponse");
return;
}
callback.success(response.body());
}
@Override
public void onFailure(@NonNull Call<List<MispUser>> call, @NonNull Throwable t) {
callback.failure(extractError(t));
}
});
}
public void getAllUsers(final AllUsersCallback callback) { public void getAllUsers(final AllUsersCallback callback) {
Call<List<MispUser>> call = mispRestInterface.getAllUsers(); Call<List<MispUser>> call = mispService.getAllUsers();
call.enqueue(new Callback<List<MispUser>>() { call.enqueue(new Callback<List<MispUser>>() {
@Override @Override
@ -305,7 +322,7 @@ public class MispRestClient {
User[] users = new User[mispUsers.size()]; User[] users = new User[mispUsers.size()];
for (int i = 0; i < users.length; i++) { for (int i = 0; i < users.length; i++) {
users[i] = mispUsers.get(i).user; users[i] = mispUsers.get(i).getUser();
} }
callback.success(users); callback.success(users);
@ -325,7 +342,7 @@ public class MispRestClient {
* @param callback {@link UserCallback} wrapper to return the created user directly * @param callback {@link UserCallback} wrapper to return the created user directly
*/ */
public void addUser(User user, final UserCallback callback) { 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>() { call.enqueue(new Callback<MispUser>() {
@Override @Override
@ -334,7 +351,7 @@ public class MispRestClient {
callback.failure(extractError(response)); callback.failure(extractError(response));
} else { } else {
assert response.body() != null; assert response.body() != null;
callback.success(response.body().user); callback.success(response.body().getUser());
} }
} }
@ -355,7 +372,7 @@ public class MispRestClient {
* @param callback {@link OrganisationCallback} wrapper to return a organisation directly * @param callback {@link OrganisationCallback} wrapper to return a organisation directly
*/ */
public void getOrganisation(int orgId, final OrganisationCallback callback) { 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>() { call.enqueue(new Callback<MispOrganisation>() {
@Override @Override
@ -400,7 +417,7 @@ public class MispRestClient {
} }
public void getAllOrganisations(final AllOrganisationsCallback callback) { public void getAllOrganisations(final AllOrganisationsCallback callback) {
Call<List<MispOrganisation>> call = mispRestInterface.getAllOrganisations(); Call<List<MispOrganisation>> call = mispService.getAllOrganisations();
call.enqueue(new Callback<List<MispOrganisation>>() { call.enqueue(new Callback<List<MispOrganisation>>() {
@Override @Override
@ -436,7 +453,7 @@ public class MispRestClient {
* @param callback {@link OrganisationCallback} wrapper to return the created organisation directly * @param callback {@link OrganisationCallback} wrapper to return the created organisation directly
*/ */
public void addOrganisation(Organisation organisation, final OrganisationCallback callback) { 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>() { call.enqueue(new Callback<MispOrganisation>() {
@Override @Override
@ -464,7 +481,7 @@ public class MispRestClient {
* @param callback {@link OrganisationCallback} wrapper to return a list of servers directly * @param callback {@link OrganisationCallback} wrapper to return a list of servers directly
*/ */
public void getAllServers(final AllServersCallback callback) { public void getAllServers(final AllServersCallback callback) {
Call<List<MispServer>> call = mispRestInterface.getAllServers(); Call<List<MispServer>> call = mispService.getAllServers();
call.enqueue(new Callback<List<MispServer>>() { call.enqueue(new Callback<List<MispServer>>() {
@Override @Override
@ -472,16 +489,14 @@ public class MispRestClient {
if (!response.isSuccessful()) { if (!response.isSuccessful()) {
callback.failure(extractError(response)); callback.failure(extractError(response));
} else { } else {
List<MispServer> mispServers = response.body(); List<MispServer> mispServers = response.body();
assert mispServers != null; assert mispServers != null;
Server[] servers = new Server[mispServers.size()]; Server[] servers = new Server[mispServers.size()];
for (int i = 0; i < servers.length; i++) { for (int i = 0; i < servers.length; i++) {
servers[i] = mispServers.get(i).server; servers[i] = mispServers.get(i).getServer();
} }
callback.success(servers); callback.success(servers);
} }
} }
@ -493,6 +508,26 @@ public class MispRestClient {
}); });
} }
public void getAllServers(final AllRawServersCallback callback) {
Call<List<MispServer>> call = mispService.getAllServers();
call.enqueue(new Callback<List<MispServer>>() {
@Override
public void onResponse(@NonNull Call<List<MispServer>> call, @NonNull Response<List<MispServer>> response) {
if (!response.isSuccessful()) {
callback.failure(extractError(response));
} else {
callback.success(response.body());
}
}
@Override
public void onFailure(@NonNull Call<List<MispServer>> call, @NonNull Throwable t) {
callback.failure(t.getMessage());
}
});
}
/** /**
* Add a server to the MISP instance * Add a server to the MISP instance
* *
@ -500,7 +535,7 @@ public class MispRestClient {
* @param callback {@link ServerCallback} wrapper to return the created server directly * @param callback {@link ServerCallback} wrapper to return the created server directly
*/ */
public void addServer(Server server, final ServerCallback callback) { 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>() { call.enqueue(new Callback<Server>() {
@Override @Override
@ -586,7 +621,6 @@ public class MispRestClient {
} }
// interfaces // interfaces
public interface AvailableCallback { public interface AvailableCallback {
void available(); void available();
@ -605,6 +639,12 @@ public class MispRestClient {
void failure(String error); void failure(String error);
} }
public interface AllMispUsersCallback {
void success(List<MispUser> users);
void failure(String error);
}
public interface OrganisationCallback { public interface OrganisationCallback {
void success(Organisation organisation); void success(Organisation organisation);
@ -629,9 +669,15 @@ public class MispRestClient {
void failure(String error); void failure(String error);
} }
public interface AllRawServersCallback {
void success(List<MispServer> mispServers);
void failure(String error);
}
public interface AllRolesCallback { public interface AllRolesCallback {
void success(Role[] roles); void success(Role[] roles);
void failure(String error); void failure(String error);
} }
} }

View File

@ -1,5 +1,6 @@
package lu.circl.mispbump.auxiliary; package lu.circl.mispbump.auxiliary;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@ -20,12 +21,13 @@ import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException; import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException; 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.Organisation;
import lu.circl.mispbump.models.restModels.Role; import lu.circl.mispbump.models.restModels.Role;
import lu.circl.mispbump.models.restModels.User; import lu.circl.mispbump.models.restModels.User;
import lu.circl.mispbump.security.KeyStoreWrapper; import lu.circl.mispbump.security.KeyStoreWrapper;
public class PreferenceManager { public class PreferenceManager {
private static final String PREFERENCES_FILE = "user_settings"; private static final String PREFERENCES_FILE = "user_settings";
@ -34,7 +36,7 @@ public class PreferenceManager {
private static final String USER_INFOS = "user_infos"; private static final String USER_INFOS = "user_infos";
private static final String USER_ORG_INFOS = "user_org_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"; private static final String MISP_ROLES = "misp_roles";
@ -81,10 +83,10 @@ public class PreferenceManager {
public Role[] getRoles() { public Role[] getRoles() {
Type type = new TypeToken<Role[]>() { Type type = new TypeToken<Role[]>() {
}.getType(); }.getType();
String rolesString = preferences.getString(MISP_ROLES, "");
assert rolesString != null; String rolesString = preferences.getString(MISP_ROLES, null);
if (rolesString.isEmpty()) {
if (rolesString == null) {
return null; return null;
} else { } else {
return new Gson().fromJson(rolesString, type); return new Gson().fromJson(rolesString, type);
@ -97,23 +99,13 @@ public class PreferenceManager {
* *
* @param user {@link User} * @param user {@link User}
*/ */
public void setUserInfo(User user) { public void setMyUser(User user) {
try { 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(); 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(); editor.apply();
} catch (NoSuchPaddingException e) { } catch (BadPaddingException | InvalidKeyException | NoSuchAlgorithmException | IllegalBlockSizeException | NoSuchPaddingException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
@ -133,17 +125,7 @@ public class PreferenceManager {
KeyStoreWrapper keyStoreWrapper = new KeyStoreWrapper(KeyStoreWrapper.USER_INFO_ALIAS); KeyStoreWrapper keyStoreWrapper = new KeyStoreWrapper(KeyStoreWrapper.USER_INFO_ALIAS);
String decrypted = keyStoreWrapper.decrypt(preferences.getString(USER_INFOS, "")); String decrypted = keyStoreWrapper.decrypt(preferences.getString(USER_INFOS, ""));
return new Gson().fromJson(decrypted, User.class); return new Gson().fromJson(decrypted, User.class);
} catch (NoSuchPaddingException e) { } catch (BadPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchAlgorithmException | IllegalBlockSizeException | NoSuchPaddingException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace(); e.printStackTrace();
} }
@ -156,7 +138,7 @@ public class PreferenceManager {
* *
* @param organisation Object representation of json organisation information * @param organisation Object representation of json organisation information
*/ */
public void setUserOrgInfo(Organisation organisation) { public void setMyOrganisation(Organisation organisation) {
try { try {
String orgStr = new Gson().toJson(organisation); String orgStr = new Gson().toJson(organisation);
KeyStoreWrapper keyStoreWrapper = new KeyStoreWrapper(KeyStoreWrapper.USER_ORGANISATION_INFO_ALIAS); KeyStoreWrapper keyStoreWrapper = new KeyStoreWrapper(KeyStoreWrapper.USER_ORGANISATION_INFO_ALIAS);
@ -229,7 +211,8 @@ public class PreferenceManager {
try { try {
KeyStoreWrapper keyStoreWrapper = new KeyStoreWrapper(KeyStoreWrapper.USER_CREDENTIALS_ALIAS); KeyStoreWrapper keyStoreWrapper = new KeyStoreWrapper(KeyStoreWrapper.USER_CREDENTIALS_ALIAS);
Type type = new TypeToken<Pair<String, String>>() {}.getType(); Type type = new TypeToken<Pair<String, String>>() {
}.getType();
String serializedCreds = keyStoreWrapper.decrypt(preferences.getString(USER_CREDENTIALS, "")); String serializedCreds = keyStoreWrapper.decrypt(preferences.getString(USER_CREDENTIALS, ""));
return new Gson().fromJson(serializedCreds, type); return new Gson().fromJson(serializedCreds, type);
} catch (InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException e) { } catch (InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException e) {
@ -240,58 +223,57 @@ public class PreferenceManager {
} }
private List<UploadInformation> cachedUploadInformationList; private List<SyncInformation> cachedSyncInformationList;
private void loadUploadInformationList() { private void loadSyncInformationList() {
KeyStoreWrapper ksw = new KeyStoreWrapper(KeyStoreWrapper.UPLOAD_INFORMATION_ALIAS); KeyStoreWrapper ksw = new KeyStoreWrapper(KeyStoreWrapper.SYNC_INFORMATION_ALIAS);
String storedUploadInfoString = preferences.getString(UPLOAD_INFO, null); String storedSyncInfoString = preferences.getString(SYNC_INFO, null);
Type type = new TypeToken<List<UploadInformation>>() { Type type = new TypeToken<List<SyncInformation>>() {}.getType();
}.getType();
if (storedUploadInfoString == null || storedUploadInfoString.isEmpty()) { if (storedSyncInfoString == null || storedSyncInfoString.isEmpty()) {
cachedUploadInformationList = new ArrayList<>(); cachedSyncInformationList = new ArrayList<>();
} else { } else {
try { try {
storedUploadInfoString = ksw.decrypt(storedUploadInfoString); storedSyncInfoString = ksw.decrypt(storedSyncInfoString);
cachedUploadInformationList = new Gson().fromJson(storedUploadInfoString, type); cachedSyncInformationList = new Gson().fromJson(storedSyncInfoString, type);
} catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException | NoSuchAlgorithmException e) { } catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException | NoSuchAlgorithmException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
} }
private void saveUploadInformationList() { private void saveSyncInformationList() {
try { try {
KeyStoreWrapper ksw = new KeyStoreWrapper(KeyStoreWrapper.UPLOAD_INFORMATION_ALIAS); KeyStoreWrapper ksw = new KeyStoreWrapper(KeyStoreWrapper.SYNC_INFORMATION_ALIAS);
String cipherText = ksw.encrypt(new Gson().toJson(cachedUploadInformationList)); String cipherText = ksw.encrypt(new Gson().toJson(cachedSyncInformationList));
SharedPreferences.Editor editor = preferences.edit(); SharedPreferences.Editor editor = preferences.edit();
editor.putString(UPLOAD_INFO, cipherText); editor.putString(SYNC_INFO, cipherText);
editor.apply(); editor.apply();
} catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException e) { } catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
public List<UploadInformation> getUploadInformationList() { public List<SyncInformation> getSyncInformationList() {
if (cachedUploadInformationList == null) { if (cachedSyncInformationList == null) {
loadUploadInformationList(); loadSyncInformationList();
} }
return cachedUploadInformationList; return cachedSyncInformationList;
} }
public void setUploadInformationList(List<UploadInformation> uploadInformationList) { public void setSyncInformationList(List<SyncInformation> uploadInformationList) {
cachedUploadInformationList = uploadInformationList; cachedSyncInformationList = uploadInformationList;
saveUploadInformationList(); saveSyncInformationList();
} }
public UploadInformation getUploadInformation(UUID uuid) { public SyncInformation getSyncInformation(UUID uuid) {
if (cachedUploadInformationList == null) { if (cachedSyncInformationList == null) {
loadUploadInformationList(); loadSyncInformationList();
} }
for (UploadInformation ui : cachedUploadInformationList) { for (SyncInformation ui : cachedSyncInformationList) {
if (ui.getUuid().compareTo(uuid) == 0) { if (ui.getUuid().compareTo(uuid) == 0) {
return ui; return ui;
} }
@ -300,51 +282,50 @@ public class PreferenceManager {
return null; return null;
} }
public void addUploadInformation(UploadInformation uploadInformation) { public void addSyncInformation(SyncInformation syncInformation) {
if (cachedUploadInformationList == null) { if (cachedSyncInformationList == null) {
loadUploadInformationList(); loadSyncInformationList();
} }
// update if exists // update if exists
for (int i = 0; i < cachedUploadInformationList.size(); i++) { for (int i = 0; i < cachedSyncInformationList.size(); i++) {
if (cachedUploadInformationList.get(i).getUuid().compareTo(uploadInformation.getUuid()) == 0) { if (cachedSyncInformationList.get(i).getUuid().compareTo(syncInformation.getUuid()) == 0) {
cachedUploadInformationList.set(i, uploadInformation); cachedSyncInformationList.set(i, syncInformation);
saveUploadInformationList(); saveSyncInformationList();
return; return;
} }
} }
// else: add cachedSyncInformationList.add(syncInformation);
cachedUploadInformationList.add(uploadInformation); saveSyncInformationList();
saveUploadInformationList();
} }
public void removeUploadInformation(UUID uuid) { public void removeUploadInformation(UUID uuid) {
if (cachedUploadInformationList == null) { if (cachedSyncInformationList == null) {
loadUploadInformationList(); loadSyncInformationList();
} }
for (UploadInformation ui : cachedUploadInformationList) { for (SyncInformation ui : cachedSyncInformationList) {
if (ui.getUuid().compareTo(uuid) == 0) { if (ui.getUuid().compareTo(uuid) == 0) {
// if is last element, then clear everything including IV and key in KeyStore // if is last element, then clear everything including IV and key in KeyStore
if (cachedUploadInformationList.size() == 1) { if (cachedSyncInformationList.size() == 1) {
clearUploadInformation(); clearUploadInformation();
} else { } else {
cachedUploadInformationList.remove(ui); cachedSyncInformationList.remove(ui);
saveUploadInformationList(); saveSyncInformationList();
} }
} }
} }
} }
public void clearUploadInformation() { 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(); keyStoreWrapper.deleteStoredKey();
SharedPreferences.Editor editor = preferences.edit(); SharedPreferences.Editor editor = preferences.edit();
editor.remove(UPLOAD_INFO); editor.remove(SYNC_INFO);
editor.apply(); editor.apply();
} }
@ -359,4 +340,4 @@ public class PreferenceManager {
editor.clear(); editor.clear();
editor.apply(); editor.apply();
} }
} }

View File

@ -1,5 +1,6 @@
package lu.circl.mispbump.auxiliary; package lu.circl.mispbump.auxiliary;
import android.app.Activity; import android.app.Activity;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Point; import android.graphics.Point;
@ -13,6 +14,7 @@ import com.google.zxing.common.BitMatrix;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
public class QrCodeGenerator { public class QrCodeGenerator {
private Activity callingActivity; private Activity callingActivity;
@ -32,7 +34,7 @@ public class QrCodeGenerator {
size = displaySize.y; size = displaySize.y;
} }
size = (int)(size * 0.8); size = (int) (size * 0.8);
try { try {
MultiFormatWriter multiFormatWriter = new MultiFormatWriter(); MultiFormatWriter multiFormatWriter = new MultiFormatWriter();

View File

@ -1,9 +1,12 @@
package lu.circl.mispbump.auxiliary; package lu.circl.mispbump.auxiliary;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import java.util.Random; import java.util.Random;
public class RandomString { public class RandomString {
@SuppressWarnings("SpellCheckingInspection") @SuppressWarnings("SpellCheckingInspection")
private static final String upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static final String upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

View File

@ -1,12 +1,6 @@
package lu.circl.mispbump.auxiliary package lu.circl.mispbump.auxiliary
import android.graphics.Bitmap import android.graphics.*
import android.graphics.BitmapShader
import android.graphics.Canvas
import android.graphics.ColorFilter
import android.graphics.Paint
import android.graphics.PixelFormat
import android.graphics.Shader
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
@ -46,4 +40,4 @@ class TileDrawable(drawable: Drawable, tileMode: Shader.TileMode) : Drawable() {
return bmp return bmp
} }
} }

View File

@ -20,6 +20,7 @@ import android.content.Context;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.TextureView; import android.view.TextureView;
/** /**
* A {@link TextureView} that can be adjusted to a specified aspect ratio. * A {@link TextureView} that can be adjusted to a specified aspect ratio.
*/ */

View File

@ -1,5 +1,6 @@
package lu.circl.mispbump.customViews; package lu.circl.mispbump.customViews;
import android.content.Context; import android.content.Context;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.MotionEvent; import android.view.MotionEvent;
@ -9,7 +10,6 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout;
import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.bottomsheet.BottomSheetBehavior;
import java.util.ConcurrentModificationException;
/** /**
* Can disable touch input on bottom sheet. * Can disable touch input on bottom sheet.

View File

@ -1,5 +1,6 @@
package lu.circl.mispbump.customViews; package lu.circl.mispbump.customViews;
import android.content.Context; import android.content.Context;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.MotionEvent; import android.view.MotionEvent;
@ -8,6 +9,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.viewpager.widget.ViewPager; import androidx.viewpager.widget.ViewPager;
public class ExtendedViewPager extends ViewPager { public class ExtendedViewPager extends ViewPager {
private boolean swipeEnabled; private boolean swipeEnabled;

View File

@ -1,5 +1,6 @@
package lu.circl.mispbump.customViews; package lu.circl.mispbump.customViews;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.util.AttributeSet; import android.util.AttributeSet;
@ -7,6 +8,7 @@ import android.widget.FrameLayout;
import lu.circl.mispbump.R; import lu.circl.mispbump.R;
public class FixedAspectRatioFrameLayout extends FrameLayout { public class FixedAspectRatioFrameLayout extends FrameLayout {
private int mAspectRatioWidth; private int mAspectRatioWidth;
private int mAspectRatioHeight; private int mAspectRatioHeight;
@ -56,4 +58,4 @@ public class FixedAspectRatioFrameLayout extends FrameLayout {
MeasureSpec.makeMeasureSpec(finalWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(finalWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(finalHeight, MeasureSpec.EXACTLY)); MeasureSpec.makeMeasureSpec(finalHeight, MeasureSpec.EXACTLY));
} }
} }

View File

@ -1,7 +1,9 @@
package lu.circl.mispbump.customViews; package lu.circl.mispbump.customViews;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.text.method.PasswordTransformationMethod; import android.text.method.PasswordTransformationMethod;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -13,10 +15,10 @@ import androidx.constraintlayout.widget.ConstraintLayout;
import lu.circl.mispbump.R; import lu.circl.mispbump.R;
public class MaterialPasswordView extends ConstraintLayout { public class MaterialPasswordView extends ConstraintLayout {
private TextView titleView, passwordView; private TextView titleView, passwordView;
private OnCopyClickListener onCopyClickListener;
public MaterialPasswordView(Context context, AttributeSet attrs) { public MaterialPasswordView(Context context, AttributeSet attrs) {
@ -29,14 +31,6 @@ public class MaterialPasswordView extends ConstraintLayout {
final String password = a.getString(R.styleable.MaterialPasswordView_password); final String password = a.getString(R.styleable.MaterialPasswordView_password);
a.recycle(); 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 = view.findViewById(R.id.material_password_title);
titleView.setText(title); titleView.setText(title);
@ -45,14 +39,19 @@ public class MaterialPasswordView extends ConstraintLayout {
passwordView.setText(password); passwordView.setText(password);
ImageButton visibleToggle = findViewById(R.id.visibleToggle); ImageButton visibleToggle = findViewById(R.id.visibleToggle);
visibleToggle.setOnClickListener(new OnClickListener() {
@Override AnimatedVectorDrawable lookAway = (AnimatedVectorDrawable) context.getDrawable(R.drawable.animated_eye_to_up);
public void onClick(View v) { AnimatedVectorDrawable lookCenter = (AnimatedVectorDrawable) context.getDrawable(R.drawable.animated_eye_to_center);
if (passwordView.getTransformationMethod() == null) {
passwordView.setTransformationMethod(new PasswordTransformationMethod()); visibleToggle.setOnClickListener(v -> {
} else { if (passwordView.getTransformationMethod() == null) {
passwordView.setTransformationMethod(null); passwordView.setTransformationMethod(new PasswordTransformationMethod());
} visibleToggle.setImageDrawable(lookCenter);
lookCenter.start();
} else {
passwordView.setTransformationMethod(null);
visibleToggle.setImageDrawable(lookAway);
lookAway.start();
} }
}); });
} }
@ -82,13 +81,4 @@ public class MaterialPasswordView extends ConstraintLayout {
} }
} }
public void addOnCopyClickedListener(OnCopyClickListener listener) {
onCopyClickListener = listener;
}
public interface OnCopyClickListener {
void onClick(String title, String password);
}
} }

View File

@ -1,5 +1,6 @@
package lu.circl.mispbump.customViews; package lu.circl.mispbump.customViews;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.util.AttributeSet; import android.util.AttributeSet;
@ -13,18 +14,18 @@ import androidx.constraintlayout.widget.ConstraintLayout;
import lu.circl.mispbump.R; import lu.circl.mispbump.R;
public class MaterialPreferenceSwitch extends ConstraintLayout {
private View rootView; public class MaterialPreferenceSwitch extends ConstraintLayout {
private TextView titleView, subTitleView; private TextView titleView, subTitleView;
private Switch switchView; private Switch switchView;
private CompoundButton.OnCheckedChangeListener onCheckedChangeListener;
public MaterialPreferenceSwitch(Context context, AttributeSet attrs) { public MaterialPreferenceSwitch(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
View view = LayoutInflater.from(context).inflate(R.layout.material_preference_switch, this); View view = LayoutInflater.from(context).inflate(R.layout.material_preference_switch, this);
rootView = view.findViewById(R.id.rootLayout); View rootView = view.findViewById(R.id.rootLayout);
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.MaterialPreferenceSwitch); TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.MaterialPreferenceSwitch);
String title = a.getString(R.styleable.MaterialPreferenceSwitch_title); String title = a.getString(R.styleable.MaterialPreferenceSwitch_title);
@ -40,22 +41,29 @@ public class MaterialPreferenceSwitch extends ConstraintLayout {
switchView = view.findViewById(R.id.material_preference_switch); switchView = view.findViewById(R.id.material_preference_switch);
rootView.setOnClickListener(new OnClickListener() { rootView.setOnClickListener(v -> {
@Override if (switchView.isEnabled()) {
public void onClick(View v) { switchView.setChecked(!switchView.isChecked());
if (switchView.isEnabled()) {
switchView.setChecked(!switchView.isChecked());
}
} }
}); });
switchView.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { switchView.setOnCheckedChangeListener((buttonView, isChecked) -> {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (onCheckedChangeListener != null) {
if (isChecked) { onCheckedChangeListener.onCheckedChanged(buttonView, isChecked);
}
if (isChecked) {
if (!onText.isEmpty()) {
subTitleView.setText(onText); subTitleView.setText(onText);
} else { } else {
subTitleView.setVisibility(GONE);
}
} else {
if (!offText.isEmpty()) {
subTitleView.setText(offText); subTitleView.setText(offText);
} else {
subTitleView.setVisibility(GONE);
} }
} }
}); });
@ -73,4 +81,8 @@ public class MaterialPreferenceSwitch extends ConstraintLayout {
return switchView.isChecked(); return switchView.isChecked();
} }
public void setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener onCheckedChangeListener) {
this.onCheckedChangeListener = onCheckedChangeListener;
}
} }

View File

@ -1,5 +1,6 @@
package lu.circl.mispbump.customViews; package lu.circl.mispbump.customViews;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
@ -14,6 +15,7 @@ import androidx.constraintlayout.widget.ConstraintLayout;
import lu.circl.mispbump.R; import lu.circl.mispbump.R;
public class MaterialPreferenceText extends ConstraintLayout { public class MaterialPreferenceText extends ConstraintLayout {
private View rootView; private View rootView;
@ -32,7 +34,7 @@ public class MaterialPreferenceText extends ConstraintLayout {
icon = view.findViewById(R.id.material_preference_src); icon = view.findViewById(R.id.material_preference_src);
int imageRes = a.getResourceId(R.styleable.MaterialPreferenceText_pref_icon, 0x0); int imageRes = a.getResourceId(R.styleable.MaterialPreferenceText_pref_icon, 0x0);
if (imageRes != 0x0){ if (imageRes != 0x0) {
icon.setImageResource(imageRes); icon.setImageResource(imageRes);
} else { } else {
icon.setVisibility(GONE); icon.setVisibility(GONE);

View File

@ -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;
}
}

View File

@ -1,8 +1,8 @@
package lu.circl.mispbump.customViews; package lu.circl.mispbump.customViews;
import android.content.Context; import android.content.Context;
import android.content.res.ColorStateList; import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@ -15,6 +15,7 @@ import androidx.core.widget.ImageViewCompat;
import lu.circl.mispbump.R; import lu.circl.mispbump.R;
public class UploadAction extends ConstraintLayout { public class UploadAction extends ConstraintLayout {
private Context context; private Context context;
@ -43,15 +44,15 @@ public class UploadAction extends ConstraintLayout {
View baseView = LayoutInflater.from(context).inflate(R.layout.view_upload_action, this); 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); // a.recycle();
titleView.setText(a.getString(R.styleable.UploadAction_description));
a.recycle();
errorView = baseView.findViewById(R.id.error); errorView = baseView.findViewById(R.id.error);
stateView = baseView.findViewById(R.id.stateView); stateView = baseView.findViewById(R.id.progressIcon);
progressBar = baseView.findViewById(R.id.progressBar); progressBar = baseView.findViewById(R.id.progressBar);
setCurrentUploadState(UploadState.PENDING); setCurrentUploadState(UploadState.PENDING);
@ -64,6 +65,7 @@ public class UploadAction extends ConstraintLayout {
/** /**
* Displays an error message for the upload action. * Displays an error message for the upload action.
*
* @param error a string to show or null to hide * @param error a string to show or null to hide
*/ */
public void setError(String error) { public void setError(String error) {

View File

@ -1,5 +1,6 @@
package lu.circl.mispbump.fragments; package lu.circl.mispbump.fragments;
import android.Manifest; import android.Manifest;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
@ -62,6 +63,7 @@ import java.util.concurrent.TimeUnit;
import lu.circl.mispbump.R; import lu.circl.mispbump.R;
import lu.circl.mispbump.customViews.AutoFitTextureView; import lu.circl.mispbump.customViews.AutoFitTextureView;
public class CameraFragment extends Fragment implements ActivityCompat.OnRequestPermissionsResultCallback { public class CameraFragment extends Fragment implements ActivityCompat.OnRequestPermissionsResultCallback {
private class ImageProcessingThread extends Thread { private class ImageProcessingThread extends Thread {

View File

@ -1,75 +0,0 @@
package lu.circl.mispbump.fragments;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
public class SyncFragmentAdapter extends FragmentPagerAdapter {
public CameraFragment cameraFragment_1, cameraFragment_2;
private UploadSettingsFragment uploadSettingsFragment;
public SyncFragmentAdapter(@NonNull FragmentManager fm) {
super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
}
private CameraFragment.QrScanCallback scanCallback;
@NonNull
@Override
public Fragment getItem(int position) {
switch (position) {
case 0:
if (cameraFragment_1 == null) {
cameraFragment_1 = new CameraFragment();
}
if (scanCallback != null) {
cameraFragment_1.setOnQrAvailableListener(scanCallback);
}
return cameraFragment_1;
case 1:
if (cameraFragment_2 == null) {
cameraFragment_2 = new CameraFragment();
}
if (scanCallback != null) {
cameraFragment_1.setOnQrAvailableListener(scanCallback);
}
return cameraFragment_2;
case 2:
if (uploadSettingsFragment == null) {
uploadSettingsFragment = new UploadSettingsFragment();
}
return uploadSettingsFragment;
default:
return new CameraFragment();
}
}
public void setQrReceivedCallback(CameraFragment.QrScanCallback qrScanCallback) {
this.scanCallback = qrScanCallback;
}
public void disableCameraPreview() {
if (cameraFragment_1 != null) {
// cameraFragment_1.disablePreview();
}
if (cameraFragment_2 != null) {
// cameraFragment_2.disablePreview();
}
}
@Override
public int getCount() {
return 3;
}
}

View File

@ -1,65 +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;
}
}

View File

@ -1,88 +0,0 @@
package lu.circl.mispbump.fragments;
import android.os.Bundle;
import android.util.Log;
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);
}
}

View File

@ -1,5 +1,6 @@
package lu.circl.mispbump.interfaces; package lu.circl.mispbump.interfaces;
import java.util.List; import java.util.List;
import lu.circl.mispbump.models.restModels.MispOrganisation; import lu.circl.mispbump.models.restModels.MispOrganisation;
@ -16,10 +17,11 @@ import retrofit2.http.GET;
import retrofit2.http.POST; import retrofit2.http.POST;
import retrofit2.http.Path; import retrofit2.http.Path;
/** /**
* RetroFit2 interface for communication with misp instances * RetroFit2 interface for communication with misp instances
*/ */
public interface MispRestInterface { public interface MispService {
// settings routes // settings routes
@ -61,4 +63,4 @@ public interface MispRestInterface {
@POST("servers/add") @POST("servers/add")
Call<Server> addServer(@Body Server server); Call<Server> addServer(@Body Server server);
} }

View File

@ -1,7 +1,9 @@
package lu.circl.mispbump.interfaces; package lu.circl.mispbump.interfaces;
import android.view.View; import android.view.View;
public interface OnRecyclerItemClickListener<T> { public interface OnRecyclerItemClickListener<T> {
void onClick(View v, T item); void onClick(View v, T item);
} }

View File

@ -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();
}
}

View File

@ -1,29 +1,96 @@
package lu.circl.mispbump.models; package lu.circl.mispbump.models;
import lu.circl.mispbump.models.restModels.Organisation;
import androidx.annotation.NonNull;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.UUID;
/** /**
* A Class that holds the information needed synchronize two misp instances. * Class that holds the information needed synchronize two misp instances.
* This class can be serialized and passed via QR code.
*/ */
public class SyncInformation { public class SyncInformation {
public Organisation organisation; private UUID uuid;
public String syncUserEmail; private Date date, lastModified;
public String syncUserPassword;
public String syncUserAuthkey;
public String baseUrl;
public SyncInformation() {} private ExchangeInformation remote;
private ExchangeInformation local;
private boolean syncedWithRemote;
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 void setSyncedWithRemote(boolean syncedWithRemote) {
this.syncedWithRemote = syncedWithRemote;
}
public boolean isSyncedWithRemote() {
return syncedWithRemote;
}
public ExchangeInformation getRemote() {
return remote;
}
public void setRemote(ExchangeInformation remote) {
this.remote = remote;
}
public ExchangeInformation getLocal() {
return local;
}
public void setLocal(ExchangeInformation local) {
this.local = local;
}
@NonNull
@Override @Override
public String toString() { public String toString() {
return "SyncInformation{" + return "Sync Information: \n" +
"organisation=" + organisation + "UUID = " + uuid + "\n" +
", syncUserEmail='" + syncUserEmail + '\'' + "Sync Date = " + getSyncDateString() + "\n" +
", syncUserPassword='" + syncUserPassword + '\'' + remote.toString() +
", syncUserAuthkey='" + syncUserAuthkey + '\'' + local.toString();
", baseUrl='" + baseUrl + '\'' +
'}';
} }
} }

View File

@ -1,117 +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 +
'}';
}
}

View File

@ -1,8 +1,10 @@
package lu.circl.mispbump.models.restModels; package lu.circl.mispbump.models.restModels;
import com.google.gson.annotations.Expose; import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
public class MispOrganisation { public class MispOrganisation {
@SerializedName("Organisation") @SerializedName("Organisation")
@Expose @Expose

View File

@ -1,8 +1,10 @@
package lu.circl.mispbump.models.restModels; package lu.circl.mispbump.models.restModels;
import com.google.gson.annotations.Expose; import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
public class MispRole { public class MispRole {
@SerializedName("Role") @SerializedName("Role")
@Expose @Expose

View File

@ -1,30 +1,59 @@
package lu.circl.mispbump.models.restModels; package lu.circl.mispbump.models.restModels;
import java.util.List;
import com.google.gson.annotations.Expose; import androidx.annotation.NonNull;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import java.util.List;
public class MispServer { public class MispServer {
public MispServer() {} @SerializedName("Server")
private Server server;
public MispServer(Server server, Organisation organisation, Organisation remoteOrganisation) { @SerializedName("Organisation")
private Organisation organisation;
@SerializedName("RemoteOrg")
private Organisation remoteOrganisation;
@SerializedName("User")
private List<User> user;
public Server getServer() {
return server;
}
public void setServer(Server server) {
this.server = server; this.server = server;
this.organisation = organisation;
this.remoteOrg = remoteOrganisation;
} }
@SerializedName("Server") public Organisation getOrganisation() {
@Expose return organisation;
public Server server; }
@SerializedName("Organisation") public void setOrganisation(Organisation organisation) {
@Expose this.organisation = organisation;
public Organisation organisation; }
@SerializedName("RemoteOrg")
@Expose
public Organisation remoteOrg;
@SerializedName("User")
@Expose
public List<User> user;
} public Organisation getRemoteOrganisation() {
return remoteOrganisation;
}
public void setRemoteOrganisation(Organisation remoteOrganisation) {
this.remoteOrganisation = remoteOrganisation;
}
public List<User> getUser() {
return user;
}
public void setUser(List<User> user) {
this.user = user;
}
@NonNull
@Override
public String toString() {
return server.toString() + "\n" + organisation.toString() + "\n" + remoteOrganisation.toString();
}
}

View File

@ -1,15 +1,40 @@
package lu.circl.mispbump.models.restModels; package lu.circl.mispbump.models.restModels;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
public class MispUser { public class MispUser {
@SerializedName("User") @SerializedName("User")
@Expose private User user;
public User user;
public MispUser(User user) { @SerializedName("Role")
private Role role;
@SerializedName("Organisation")
private Organisation organisation;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user; this.user = user;
} }
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
public Organisation getOrganisation() {
return organisation;
}
public void setOrganisation(Organisation organisation) {
this.organisation = organisation;
}
}

View File

@ -1,5 +1,11 @@
package lu.circl.mispbump.models.restModels; package lu.circl.mispbump.models.restModels;
import androidx.annotation.NonNull;
import java.util.Arrays;
/** /**
* Information gathered from Misp API about a organisation. * Information gathered from Misp API about a organisation.
*/ */
@ -20,27 +26,14 @@ public class Organisation {
private String created_by; private String created_by;
private Integer user_count; private Integer user_count;
public Organisation() {
}
public Organisation(String name) {
this.name = name;
}
public Organisation(String name, String description) {
this.name = name;
this.description = description;
}
public Organisation toSyncOrganisation() { public Organisation toSyncOrganisation() {
Organisation organisation = new Organisation(); Organisation organisation = new Organisation();
organisation.local = true;
organisation.name = name; organisation.name = name;
organisation.uuid = uuid; organisation.uuid = uuid;
organisation.description = description; organisation.description = description;
organisation.nationality = nationality; organisation.nationality = nationality;
organisation.sector = sector; organisation.sector = sector;
organisation.type = "Sync organisation";
organisation.contacts = contacts; organisation.contacts = contacts;
return organisation; return organisation;
@ -63,19 +56,19 @@ public class Organisation {
this.name = name; this.name = name;
} }
public String getDate_created() { public String getDateCreated() {
return date_created; return date_created;
} }
public void setDate_created(String date_created) { public void setDateCreated(String date_created) {
this.date_created = date_created; this.date_created = date_created;
} }
public String getDate_modified() { public String getDateModified() {
return date_modified; return date_modified;
} }
public void setDate_modified(String date_modified) { public void setDateModified(String date_modified) {
this.date_modified = date_modified; this.date_modified = date_modified;
} }
@ -135,47 +128,47 @@ public class Organisation {
this.uuid = uuid; this.uuid = uuid;
} }
public String[] getRestricted_to_domain() { public String[] getRestrictedToDomain() {
return restricted_to_domain; return restricted_to_domain;
} }
public void setRestricted_to_domain(String[] restricted_to_domain) { public void setRestrictedToDomain(String[] restricted_to_domain) {
this.restricted_to_domain = restricted_to_domain; this.restricted_to_domain = restricted_to_domain;
} }
public String getCreated_by() { public String getCreatedBy() {
return created_by; return created_by;
} }
public void setCreated_by(String created_by) { public void setCreatedBy(String created_by) {
this.created_by = created_by; this.created_by = created_by;
} }
public Integer getUser_count() { public Integer getUserCount() {
return user_count; return user_count;
} }
public void setUser_count(Integer user_count) { public void setUserCount(Integer user_count) {
this.user_count = user_count; this.user_count = user_count;
} }
@NonNull
@Override @Override
public String toString() { public String toString() {
return "Organisation{" + return "Organisation: \n" +
"id=" + id + "\t id = " + id + "\n" +
", name='" + name + '\'' + "\t name = " + name + '\n' +
", date_created='" + date_created + '\'' + "\t date_created = " + date_created + '\n' +
", date_modified='" + date_modified + '\'' + "\t date_modified = " + date_modified + '\n' +
", type='" + type + '\'' + "\t type = " + type + '\n' +
", nationality='" + nationality + '\'' + "\t nationality = " + nationality + '\n' +
", sector='" + sector + '\'' + "\t sector = " + sector + '\n' +
", contacts='" + contacts + '\'' + "\t contacts = " + contacts + '\n' +
", description='" + description + '\'' + "\t description = " + description + '\n' +
", local=" + local + "\t local = " + local + '\n' +
", uuid='" + uuid + '\'' + "\t uuid = " + uuid + '\n' +
", restricted_to_domain='" + restricted_to_domain + '\'' + "\t restricted_to_domain = " + Arrays.toString(restricted_to_domain) + '\n' +
", created_by='" + created_by + '\'' + "\t created_by = " + created_by + '\n' +
", user_count=" + user_count + "\t user_count = " + user_count;
'}';
} }
} }

View File

@ -1,7 +1,9 @@
package lu.circl.mispbump.models.restModels; package lu.circl.mispbump.models.restModels;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
public class Role { public class Role {
@SerializedName("id") @SerializedName("id")
private Integer id; private Integer id;
@ -62,6 +64,13 @@ public class Role {
@SerializedName("permission_description") @SerializedName("permission_description")
private String permissionDescription; private String permissionDescription;
public boolean isSyncUserRole() {
return permSync && permAuth && permTagger && permTagEditor && permSharingGroup
&& permDelegate && permSighting && permPublishZmq && permPublishKafka;
}
public Integer getId() { public Integer getId() {
return id; return id;
} }

View File

@ -1,109 +1,241 @@
package lu.circl.mispbump.models.restModels; package lu.circl.mispbump.models.restModels;
import com.google.gson.annotations.SerializedName;
import androidx.annotation.NonNull;
public class Server { public class Server {
public Server() {} private Integer id;
private String name;
private String url;
private String authkey;
private Integer org_id;
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 = 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 = false;
private Boolean cache_timestamp;
public Server(String name, String url, String authkey, Integer remote_org_id) {
this.name = name; public Server(String url) {
this.url = url; this.url = url;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getAuthkey() {
return authkey;
}
public void setAuthkey(String authkey) {
this.authkey = authkey; this.authkey = authkey;
}
public Integer getOrgId() {
return org_id;
}
public void setOrgId(Integer org_id) {
this.org_id = org_id;
}
public Boolean getPush() {
return push;
}
public void setPush(Boolean push) {
this.push = push;
}
public Boolean getPull() {
return pull;
}
public void setPull(Boolean pull) {
this.pull = pull;
}
public Object getLastpulledId() {
return lastpulledid;
}
public void setLastpulledId(Object lastpulledid) {
this.lastpulledid = lastpulledid;
}
public Object getLastpushedId() {
return lastpushedid;
}
public void setLastpushedId(Object lastpushedid) {
this.lastpushedid = lastpushedid;
}
public Object getOrganization() {
return organization;
}
public void setOrganization(Object organization) {
this.organization = organization;
}
public Integer getRemoteOrgId() {
return remote_org_id;
}
public void setRemoteOrgId(Integer remote_org_id) {
this.remote_org_id = remote_org_id; this.remote_org_id = remote_org_id;
} }
@SerializedName("id") public Boolean getPublishWithoutEmail() {
public Integer id; return publish_without_email;
}
@SerializedName("name") public void setPublishWithoutEmail(Boolean publish_without_email) {
public String name; this.publish_without_email = publish_without_email;
}
@SerializedName("url") public Boolean getUnpublishEvent() {
public String url; return unpublish_event;
}
@SerializedName("authkey") public void setUnpublishEvent(Boolean unpublish_event) {
public String authkey; this.unpublish_event = unpublish_event;
}
@SerializedName("org_id") public Boolean getSelfSigned() {
public Integer org_id; return self_signed;
}
@SerializedName("push") public void setSelfSigned(Boolean self_signed) {
public Boolean push; this.self_signed = self_signed;
}
@SerializedName("pull") public String getPullRules() {
public Boolean pull; return pull_rules;
}
@SerializedName("lastpulledid") public void setPullRules(String pull_rules) {
public Object lastpulledid; this.pull_rules = pull_rules;
}
@SerializedName("lastpushedid") public String getPushRules() {
public Object lastpushedid; return push_rules;
}
@SerializedName("organization") public void setPushRules(String push_rules) {
public Object organization; this.push_rules = push_rules;
}
@SerializedName("remote_org_id") public Object getCertFile() {
public Integer remote_org_id; return cert_file;
}
@SerializedName("publish_without_email") public void setCertFile(Object cert_file) {
public Boolean publish_without_email = false; this.cert_file = cert_file;
}
@SerializedName("unpublish_event") public Object getClientCertFile() {
public Boolean unpublish_event; return client_cert_file;
}
@SerializedName("self_signed") public void setClientCertFile(Object client_cert_file) {
public Boolean self_signed = false; this.client_cert_file = client_cert_file;
}
@SerializedName("pull_rules") public Boolean getInternal() {
public String pull_rules; return internal;
}
@SerializedName("push_rules") public void setInternal(Boolean internal) {
public String push_rules; this.internal = internal;
}
@SerializedName("cert_file") public Boolean getSkipProxy() {
public Object cert_file; return skip_proxy;
}
@SerializedName("client_cert_file") public void setSkipProxy(Boolean skip_proxy) {
public Object client_cert_file; this.skip_proxy = skip_proxy;
}
@SerializedName("internal") public Boolean getCachingEnabled() {
public Boolean internal; return caching_enabled;
}
@SerializedName("skip_proxy") public void setCachingEnabled(Boolean caching_enabled) {
public Boolean skip_proxy = false; this.caching_enabled = caching_enabled;
}
@SerializedName("caching_enabled") public Boolean getCacheTimestamp() {
public Boolean caching_enabled; return cache_timestamp;
}
@SerializedName("cache_timestamp") public void setCacheTimestamp(Boolean cache_timestamp) {
public Boolean cache_timestamp; this.cache_timestamp = cache_timestamp;
}
@NonNull
@Override @Override
public String toString() { public String toString() {
return "Server{" + return "Server: \n" +
"id=" + id + "\t id = " + id + '\n' +
", name='" + name + '\'' + "\t name = " + name + '\n' +
", url='" + url + '\'' + "\t url = " + url + '\n' +
", authkey='" + authkey + '\'' + "\t authkey = " + authkey + '\n' +
", org_id=" + org_id + "\t org_id = " + org_id + '\n' +
", push=" + push + "\t push = " + push + '\n' +
", pull=" + pull + "\t pull = " + pull + '\n' +
", lastpulledid=" + lastpulledid + "\t lastpulledid = " + lastpulledid + '\n' +
", lastpushedid=" + lastpushedid + "\t lastpushedid = " + lastpushedid + '\n' +
", organization=" + organization + "\t organization = " + organization + '\n' +
", remote_org_id=" + remote_org_id + "\t remote_org_id = " + remote_org_id + '\n' +
", publish_without_email=" + publish_without_email + "\t publish_without_email = " + publish_without_email + '\n' +
", unpublish_event=" + unpublish_event + "\t unpublish_event = " + unpublish_event + '\n' +
", self_signed=" + self_signed + "\t self_signed = " + self_signed + '\n' +
", pull_rules='" + pull_rules + '\'' + "\t pull_rules = " + pull_rules + '\n' +
", push_rules='" + push_rules + '\'' + "\t push_rules = " + push_rules + '\n' +
", cert_file=" + cert_file + "\t cert_file = " + cert_file + '\n' +
", client_cert_file=" + client_cert_file + "\t client_cert_file = " + client_cert_file + '\n' +
", internal=" + internal + "\t internal = " + internal + '\n' +
", skip_proxy=" + skip_proxy + "\t skip_proxy = " + skip_proxy + '\n' +
", caching_enabled=" + caching_enabled + "\t caching_enabled = " + caching_enabled + '\n' +
", cache_timestamp=" + cache_timestamp + "\t cache_timestamp = " + cache_timestamp;
'}';
} }
} }

View File

@ -1,125 +1,249 @@
package lu.circl.mispbump.models.restModels; package lu.circl.mispbump.models.restModels;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName; import androidx.annotation.NonNull;
import lu.circl.mispbump.auxiliary.RandomString;
public class User { public class User {
public static final int ROLE_ADMIN = 1; private Integer id;
public static final int ROLE_ORG_ADMIN = 2; private String password;
public static final int ROLE_USER = 3; private Integer org_id;
public static final int ROLE_PUBLISHER = 4; private String email;
public static final int ROLE_SYNC_USER = 5; private Boolean autoalert;
public static final int ROLE_READ_ONLY = 6; private String authkey;
private String invited_by;
private Object gpgkey;
private String certif_public;
private String nids_sid;
private Boolean termsaccepted;
private String newsread;
private Integer role_id;
private String change_pw;
private Boolean contactalert;
private Boolean disabled;
private Object expiration;
private String current_login;
private String last_login;
private Boolean force_logout;
private Object date_created;
private String date_modified;
public User() {
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 User(Integer org_id, String email, Integer role_id) {
this.org_id = org_id; public Integer getId() {
this.email = email; return id;
this.role_id = role_id;
} }
public User(Integer org_id, String email, Integer role_id, String password) { public void setId(Integer id) {
this.id = id;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password; this.password = password;
}
public Integer getOrgId() {
return org_id;
}
public void setOrgId(Integer org_id) {
this.org_id = org_id; this.org_id = org_id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email; this.email = email;
}
public Boolean getAutoalert() {
return autoalert;
}
public void setAutoalert(Boolean autoalert) {
this.autoalert = autoalert;
}
public String getAuthkey() {
return authkey;
}
public void setAuthkey(String authkey) {
this.authkey = authkey;
}
public String getInvitedBy() {
return invited_by;
}
public void setInvitedBy(String invited_by) {
this.invited_by = invited_by;
}
public Object getGpgKey() {
return gpgkey;
}
public void setGpgKey(Object gpgkey) {
this.gpgkey = gpgkey;
}
public String getCertIfPublic() {
return certif_public;
}
public void setCertIfPublic(String certif_public) {
this.certif_public = certif_public;
}
public String getNidsSid() {
return nids_sid;
}
public void setNidsSid(String nids_sid) {
this.nids_sid = nids_sid;
}
public Boolean getTermsAccepted() {
return termsaccepted;
}
public void setTermsAccepted(Boolean termsaccepted) {
this.termsaccepted = termsaccepted;
}
public String getNewsRead() {
return newsread;
}
public void setNewsRead(String newsread) {
this.newsread = newsread;
}
public Integer getRoleId() {
return role_id;
}
public void setRoleId(Integer role_id) {
this.role_id = role_id; this.role_id = role_id;
} }
@SerializedName("id") public String getChangePw() {
@Expose return change_pw;
public Integer id; }
@SerializedName("password")
@Expose
public String password;
@SerializedName("org_id")
@Expose
public Integer org_id;
@SerializedName("email")
@Expose
public String email;
@SerializedName("autoalert")
@Expose
public Boolean autoalert;
@SerializedName("authkey")
@Expose
public String authkey;
@SerializedName("invited_by")
@Expose
public String invited_by;
@SerializedName("gpgkey")
@Expose
public Object gpgkey;
@SerializedName("certif_public")
@Expose
public String certif_public;
@SerializedName("nids_sid")
@Expose
public String nids_sid;
@SerializedName("termsaccepted")
@Expose
public Boolean termsaccepted;
@SerializedName("newsread")
@Expose
public String newsread;
@SerializedName("role_id")
@Expose
public Integer role_id;
@SerializedName("change_pw")
@Expose
public String change_pw;
@SerializedName("contactalert")
@Expose
public Boolean contactalert;
@SerializedName("disabled")
@Expose
public Boolean disabled;
@SerializedName("expiration")
@Expose
public Object expiration;
@SerializedName("current_login")
@Expose
public String current_login;
@SerializedName("last_login")
@Expose
public String last_login;
@SerializedName("force_logout")
@Expose
public Boolean force_logout;
@SerializedName("date_created")
@Expose
public Object date_created;
@SerializedName("date_modified")
@Expose
public String date_modified;
public void setChangePw(String change_pw) {
this.change_pw = change_pw;
}
public Boolean getContactalert() {
return contactalert;
}
public void setContactalert(Boolean contactalert) {
this.contactalert = contactalert;
}
public Boolean getDisabled() {
return disabled;
}
public void setDisabled(Boolean disabled) {
this.disabled = disabled;
}
public Object getExpiration() {
return expiration;
}
public void setExpiration(Object expiration) {
this.expiration = expiration;
}
public String getCurrentLogin() {
return current_login;
}
public void setCurrentLogin(String current_login) {
this.current_login = current_login;
}
public String getLastLogin() {
return last_login;
}
public void setLastLogin(String last_login) {
this.last_login = last_login;
}
public Boolean getForceLogout() {
return force_logout;
}
public void setForceLogout(Boolean force_logout) {
this.force_logout = force_logout;
}
public Object getDateCreated() {
return date_created;
}
public void setDateCreated(Object date_created) {
this.date_created = date_created;
}
public String getDateModified() {
return date_modified;
}
public void setDateModified(String date_modified) {
this.date_modified = date_modified;
}
@NonNull
@Override @Override
public String toString() { public String toString() {
return "User{" + return "User: \n" +
"id='" + id + '\'' + "\t id = " + id + '\n' +
", password='" + password + '\'' + "\t password = " + password + '\n' +
", org_id='" + org_id + '\'' + "\t org_id = " + org_id + '\n' +
", email='" + email + '\'' + "\t email = " + email + '\n' +
", autoalert=" + autoalert + "\t autoalert = " + autoalert + '\n' +
", authkey='" + authkey + '\'' + "\t authkey = " + authkey + '\n' +
", invited_by='" + invited_by + '\'' + "\t invited_by = " + invited_by + '\n' +
", gpgkey=" + gpgkey + "\t gpgkey = " + gpgkey + '\n' +
", certif_public='" + certif_public + '\'' + "\t certif_public = " + certif_public + '\n' +
", nids_sid='" + nids_sid + '\'' + "\t nids_sid = " + nids_sid + '\n' +
", termsaccepted=" + termsaccepted + "\t termsaccepted = " + termsaccepted + '\n' +
", newsread='" + newsread + '\'' + "\t newsread = " + newsread + '\n' +
", role_id='" + role_id + '\'' + "\t role_id = " + role_id + '\n' +
", change_pw='" + change_pw + '\'' + "\t change_pw = " + change_pw + '\n' +
", contactalert=" + contactalert + "\t contactalert = " + contactalert + '\n' +
", disabled=" + disabled + "\t disabled = " + disabled + '\n' +
", expiration=" + expiration + "\t expiration = " + expiration + '\n' +
", current_login='" + current_login + '\'' + "\t current_login = " + current_login + '\n' +
", last_login='" + last_login + '\'' + "\t last_login = " + last_login + '\n' +
", force_logout=" + force_logout + "\t force_logout = " + force_logout + '\n' +
", date_created=" + date_created + "\t date_created = " + date_created + '\n' +
", date_modified='" + date_modified + '\'' + "\t date_modified = " + date_modified;
'}';
} }
} }

View File

@ -1,7 +1,9 @@
package lu.circl.mispbump.models.restModels; package lu.circl.mispbump.models.restModels;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
public class Version { public class Version {
@SerializedName("version") @SerializedName("version")

View File

@ -1,5 +1,6 @@
package lu.circl.mispbump.security; package lu.circl.mispbump.security;
import android.util.Base64; import android.util.Base64;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -22,6 +23,7 @@ import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
/** /**
* This class provides the functionality generate a shared secret key. * This class provides the functionality generate a shared secret key.
* Furthermore it contains the encryption/decryption methods. * Furthermore it contains the encryption/decryption methods.

View File

@ -1,5 +1,6 @@
package lu.circl.mispbump.security; package lu.circl.mispbump.security;
import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties; import android.security.keystore.KeyProperties;
import android.util.Base64; import android.util.Base64;
@ -25,12 +26,13 @@ import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.GCMParameterSpec;
public class KeyStoreWrapper { public class KeyStoreWrapper {
public static final String USER_INFO_ALIAS = "ALIAS_USER_INFO"; 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_ORGANISATION_INFO_ALIAS = "ALIAS_USER_ORGANISATION_INFO";
public static final String USER_CREDENTIALS_ALIAS = "ALIAS_USER_CREDENTIALS"; 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 KEYSTORE_PROVIDER = "AndroidKeyStore";
private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding"; private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
@ -257,4 +259,4 @@ public class KeyStoreWrapper {
byte[] iv; byte[] iv;
byte[] data; byte[] data;
} }
} }

View File

@ -1,8 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"> <set
xmlns:android="http://schemas.android.com/apk/res/android">
<alpha <alpha
android:interpolator="@android:anim/accelerate_interpolator" android:interpolator="@android:anim/accelerate_interpolator"
android:fromAlpha="0.0" android:fromAlpha="0.0"
android:toAlpha="1.0" android:toAlpha="1.0"
android:duration="@android:integer/config_shortAnimTime" /> android:duration="@android:integer/config_shortAnimTime"
</set> />
</set>

View File

@ -1,8 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"> <set
xmlns:android="http://schemas.android.com/apk/res/android">
<alpha <alpha
android:interpolator="@android:anim/accelerate_interpolator" android:interpolator="@android:anim/accelerate_interpolator"
android:fromAlpha="1.0" android:fromAlpha="1.0"
android:toAlpha="0.0" android:toAlpha="0.0"
android:duration="@android:integer/config_shortAnimTime" /> android:duration="@android:integer/config_shortAnimTime"
</set> />
</set>

View File

@ -1,5 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"> <set
<translate android:fromYDelta="100%p" android:toYDelta="0" android:duration="300"/> xmlns:android="http://schemas.android.com/apk/res/android">
<alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="300" /> <translate
</set> android:fromYDelta="100%p"
android:toYDelta="0"
android:duration="300"
/>
<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0"
android:duration="300"
/>
</set>

View File

@ -1,5 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"> <set
<translate android:fromYDelta="0" android:toYDelta="-100%p" android:duration="300"/> xmlns:android="http://schemas.android.com/apk/res/android">
<alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="300" /> <translate
</set> android:fromYDelta="0"
android:toYDelta="-100%p"
android:duration="300"
/>
<alpha
android:fromAlpha="1.0"
android:toAlpha="0.0"
android:duration="300"
/>
</set>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"> <set
xmlns:android="http://schemas.android.com/apk/res/android">
<scale <scale
android:duration="500" android:duration="500"
@ -8,6 +9,7 @@
android:pivotX="50%" android:pivotX="50%"
android:pivotY="10%" android:pivotY="10%"
android:toXScale="1.0" android:toXScale="1.0"
android:toYScale="1.0" /> android:toYScale="1.0"
/>
</set> </set>

View File

@ -1,7 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"> <set
<translate android:fromXDelta="50%p" android:toXDelta="0" xmlns:android="http://schemas.android.com/apk/res/android">
android:duration="@android:integer/config_mediumAnimTime"/> <translate
<alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:fromXDelta="50%p"
android:duration="@android:integer/config_mediumAnimTime" /> android:toXDelta="0"
</set> android:duration="@android:integer/config_mediumAnimTime"
/>
<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0"
android:duration="@android:integer/config_mediumAnimTime"
/>
</set>

View File

@ -1,7 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"> <set
<translate android:fromXDelta="0" android:toXDelta="-50%p" xmlns:android="http://schemas.android.com/apk/res/android">
android:duration="@android:integer/config_mediumAnimTime"/> <translate
<alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:fromXDelta="0"
android:duration="@android:integer/config_mediumAnimTime" /> android:toXDelta="-50%p"
</set> android:duration="@android:integer/config_mediumAnimTime"
/>
<alpha
android:fromAlpha="1.0"
android:toAlpha="0.0"
android:duration="@android:integer/config_mediumAnimTime"
/>
</set>

View File

@ -7,4 +7,5 @@
android:propertyName="rotation" android:propertyName="rotation"
android:valueFrom="0" android:valueFrom="0"
android:valueTo="-180" android:valueTo="-180"
android:valueType="floatType"/> android:valueType="floatType"
/>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" > <set
xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator <objectAnimator
android:interpolator="@android:interpolator/accelerate_cubic" android:interpolator="@android:interpolator/accelerate_cubic"
@ -7,6 +8,7 @@
android:propertyName="x" android:propertyName="x"
android:valueFrom="1000" android:valueFrom="1000"
android:valueTo="0" android:valueTo="0"
android:valueType="floatType" /> android:valueType="floatType"
/>
</set> </set>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" > <set
xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator <objectAnimator
android:interpolator="@android:interpolator/accelerate_cubic" android:interpolator="@android:interpolator/accelerate_cubic"
@ -7,6 +8,7 @@
android:propertyName="x" android:propertyName="x"
android:valueFrom="-1000" android:valueFrom="-1000"
android:valueTo="0" android:valueTo="0"
android:valueType="floatType" /> android:valueType="floatType"
/>
</set> </set>

View File

@ -1,11 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" > <set
xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator <objectAnimator
android:duration="300" android:duration="300"
android:propertyName="x" android:propertyName="x"
android:valueFrom="0" android:valueFrom="0"
android:valueTo="1000" android:valueTo="1000"
android:valueType="floatType" /> android:valueType="floatType"
/>
</set> </set>

View File

@ -1,11 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" > <set
xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator <objectAnimator
android:duration="300" android:duration="300"
android:propertyName="x" android:propertyName="x"
android:valueFrom="0" android:valueFrom="0"
android:valueTo="-1000" android:valueTo="-1000"
android:valueType="floatType" /> android:valueType="floatType"
/>
</set> </set>

View File

@ -1,27 +1,33 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt" xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp" android:width="108dp"
android:height="108dp" android:height="108dp"
android:viewportHeight="108" android:viewportHeight="108"
android:viewportWidth="108"> android:viewportWidth="108"
>
<path <path
android:fillType="evenOdd" android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z" android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeColor="#00000000" android:strokeColor="#00000000"
android:strokeWidth="1"> android:strokeWidth="1"
>
<aapt:attr name="android:fillColor"> <aapt:attr name="android:fillColor">
<gradient <gradient
android:endX="78.5885" android:endX="78.5885"
android:endY="90.9159" android:endY="90.9159"
android:startX="48.7653" android:startX="48.7653"
android:startY="61.0927" android:startY="61.0927"
android:type="linear"> android:type="linear"
>
<item <item
android:color="#44000000" android:color="#44000000"
android:offset="0.0" /> android:offset="0.0"
/>
<item <item
android:color="#00000000" android:color="#00000000"
android:offset="1.0" /> android:offset="1.0"
/>
</gradient> </gradient>
</aapt:attr> </aapt:attr>
</path> </path>
@ -30,5 +36,6 @@
android:fillType="nonZero" android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z" android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeColor="#00000000" android:strokeColor="#00000000"
android:strokeWidth="1" /> android:strokeWidth="1"
/>
</vector> </vector>

View File

@ -0,0 +1,44 @@
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="vector"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group
android:name="group"
android:pivotX="12"
android:pivotY="12">
<path
android:name="cloud"
android:pathData="M 19.35 10.04 C 18.67 6.59 15.64 4 12 4 C 9.11 4 6.6 5.64 5.35 8.04 C 2.34 8.36 0 10.91 0 14 C 0 17.31 2.69 20 6 20 L 19 20 C 21.76 20 24 17.76 24 15 C 24 12.36 21.95 10.22 19.35 10.04 Z M 19 18 L 6 18 C 3.79 18 2 16.21 2 14 C 2 11.79 3.79 10 6 10 L 6.71 10 C 7.37 7.69 9.48 6 12 6 C 15.04 6 17.5 8.46 17.5 11.5 L 17.5 12 L 19 12 C 20.66 12 22 13.34 22 15 C 22 16.66 20.66 18 19 18 Z"
android:fillColor="#000"
android:strokeWidth="1" />
</group>
</vector>
</aapt:attr>
<target android:name="cloud">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="pathData"
android:duration="300"
android:valueFrom="M 14.588 4.459 C 15.395 4.755 16.136 5.187 16.785 5.726 C 17.434 6.266 17.99 6.913 18.427 7.641 C 18.863 8.369 19.18 9.178 19.35 10.04 C 19.35 10.04 19.35 10.04 19.35 10.04 C 21.95 10.22 24 12.36 24 15 C 24 17.76 21.76 20 19 20 C 14.667 20 10.333 20 6 20 C 2.69 20 0 17.31 0 14 C 0 10.91 2.34 8.36 5.35 8.04 C 6.6 5.64 9.11 4 12 4 C 12.91 4 13.782 4.162 14.588 4.459 M 6.71 10 L 6 10 C 3.79 10 2 11.79 2 14 C 2 16.21 3.79 18 6 18 L 19 18 L 19 18 C 20.66 18 22 16.66 22 15 C 22 13.34 20.66 12 19 12 L 17.5 12 L 17.5 11.5 C 17.5 8.46 15.04 6 12 6 C 9.48 6 7.37 7.69 6.71 10"
android:valueTo="M 8.12 9.29 C 9.413 10.583 10.707 11.877 12 13.17 C 13.293 11.877 14.587 10.583 15.88 9.29 C 16.27 8.9 16.9 8.9 17.29 9.29 C 17.68 9.68 17.68 10.31 17.29 10.7 C 16.525 11.465 15.76 12.23 14.995 12.995 C 14.23 13.76 13.465 14.525 12.7 15.29 C 12.31 15.68 11.68 15.68 11.29 15.29 C 9.76 13.76 8.23 12.23 6.7 10.7 C 6.31 10.31 6.31 9.68 6.7 9.29 C 7.09 8.91 7.73 8.9 8.12 9.29 C 8.12 9.29 8.12 9.29 8.12 9.29 M 11.75 12.75 L 11.75 12.75 C 11.75 12.75 11.75 12.75 11.75 12.75 C 11.75 12.75 11.75 12.75 11.75 12.75 L 11.75 12.75 L 11.75 12.75 C 11.75 12.75 11.75 12.75 11.75 12.75 C 11.75 12.75 11.75 12.75 11.75 12.75 L 11.75 12.75 L 11.75 12.75 C 11.75 12.75 11.75 12.75 11.75 12.75 C 11.75 12.75 11.75 12.75 11.75 12.75"
android:valueType="pathType"
android:interpolator="@android:interpolator/fast_out_slow_in" />
</aapt:attr>
</target>
<target android:name="group">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="rotation"
android:duration="300"
android:valueFrom="0"
android:valueTo="360"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in" />
</aapt:attr>
</target>
</animated-vector>

View File

@ -0,0 +1,44 @@
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="vector"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group
android:name="group"
android:pivotX="12"
android:pivotY="12">
<path
android:name="cloud"
android:pathData="M 19.35 10.04 C 18.67 6.59 15.64 4 12 4 C 9.11 4 6.6 5.64 5.35 8.04 C 2.34 8.36 0 10.91 0 14 C 0 17.31 2.69 20 6 20 L 19 20 C 21.76 20 24 17.76 24 15 C 24 12.36 21.95 10.22 19.35 10.04 Z M 19 18 L 6 18 C 3.79 18 2 16.21 2 14 C 2 11.79 3.79 10 6 10 L 6.71 10 C 7.37 7.69 9.48 6 12 6 C 15.04 6 17.5 8.46 17.5 11.5 L 17.5 12 L 19 12 C 20.66 12 22 13.34 22 15 C 22 16.66 20.66 18 19 18 Z"
android:fillColor="#000"
android:strokeWidth="1" />
</group>
</vector>
</aapt:attr>
<target android:name="cloud">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="pathData"
android:duration="400"
android:valueFrom="M 17.29 9.29 C 16.9 8.9 16.27 8.9 15.88 9.29 C 14.587 10.583 13.293 11.877 12 13.17 C 10.707 11.877 9.413 10.583 8.12 9.29 C 8.12 9.29 8.12 9.29 8.12 9.29 C 7.73 8.9 7.09 8.91 6.7 9.29 C 6.31 9.68 6.31 10.31 6.7 10.7 C 8.23 12.23 9.76 13.76 11.29 15.29 C 11.68 15.68 12.31 15.68 12.7 15.29 C 13.465 14.525 14.23 13.76 14.995 12.995 C 15.76 12.23 16.525 11.465 17.29 10.7 C 17.68 10.31 17.68 9.68 17.29 9.29 M 11.75 12.75 L 11.75 12.75 C 11.75 12.75 11.75 12.75 11.75 12.75 C 11.75 12.75 11.75 12.75 11.75 12.75 L 11.75 12.75 C 11.75 12.75 11.75 12.75 11.75 12.75 C 11.75 12.75 11.75 12.75 11.75 12.75 L 11.75 12.75 L 11.75 12.75 C 11.75 12.75 11.75 12.75 11.75 12.75 C 11.75 12.75 11.75 12.75 11.75 12.75 L 11.75 12.75"
android:valueTo="M 19.35 10.04 C 19.18 9.178 18.863 8.369 18.427 7.641 C 17.99 6.913 17.434 6.266 16.785 5.726 C 16.136 5.187 15.395 4.755 14.588 4.459 C 13.782 4.162 12.91 4 12 4 C 9.11 4 6.6 5.64 5.35 8.04 C 2.34 8.36 0 10.91 0 14 C 0 17.31 2.69 20 6 20 C 10.333 20 14.667 20 19 20 C 21.76 20 24 17.76 24 15 C 24 12.36 21.95 10.22 19.35 10.04 C 19.35 10.04 19.35 10.04 19.35 10.04 M 19 18 L 6 18 C 3.79 18 2 16.21 2 14 C 2 11.79 3.79 10 6 10 L 6.71 10 C 7.37 7.69 9.48 6 12 6 C 15.04 6 17.5 8.46 17.5 11.5 L 17.5 12 L 19 12 C 20.66 12 22 13.34 22 15 C 22 16.66 20.66 18 19 18 L 19 18"
android:valueType="pathType"
android:interpolator="@android:interpolator/fast_out_slow_in" />
</aapt:attr>
</target>
<target android:name="group">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="rotation"
android:duration="400"
android:valueFrom="0"
android:valueTo="0"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in" />
</aapt:attr>
</target>
</animated-vector>

View File

@ -0,0 +1,27 @@
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="vector"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:name="down"
android:pathData="M 8.12 9.29 L 12 13.17 L 15.88 9.29 C 16.27 8.9 16.9 8.9 17.29 9.29 C 17.68 9.68 17.68 10.31 17.29 10.7 L 12.7 15.29 C 12.31 15.68 11.68 15.68 11.29 15.29 L 6.7 10.7 C 6.31 10.31 6.31 9.68 6.7 9.29 C 7.09 8.91 7.73 8.9 8.12 9.29 Z"
android:fillColor="#FF000000" />
</vector>
</aapt:attr>
<target android:name="down">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="pathData"
android:duration="250"
android:valueFrom="M 8.12 9.29 L 12 13.17 L 15.88 9.29 C 16.27 8.9 16.9 8.9 17.29 9.29 C 17.68 9.68 17.68 10.31 17.29 10.7 L 12.7 15.29 C 12.31 15.68 11.68 15.68 11.29 15.29 L 6.7 10.7 C 6.31 10.31 6.31 9.68 6.7 9.29 C 7.09 8.91 7.73 8.9 8.12 9.29 Z"
android:valueTo="M 8.12 14.71 L 12 10.83 L 15.88 14.71 C 16.27 15.1 16.9 15.1 17.29 14.71 C 17.68 14.32 17.68 13.69 17.29 13.3 L 12.7 8.71 C 12.31 8.32 11.68 8.32 11.29 8.71 L 6.7 13.3 C 6.31 13.69 6.31 14.32 6.7 14.71 C 7.09 15.09 7.73 15.1 8.12 14.71 Z"
android:valueType="pathType"
android:interpolator="@android:anim/accelerate_decelerate_interpolator" />
</aapt:attr>
</target>
</animated-vector>

View File

@ -0,0 +1,27 @@
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="vector"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:name="down"
android:pathData="M 8.12 9.29 L 12 13.17 L 15.88 9.29 C 16.27 8.9 16.9 8.9 17.29 9.29 C 17.68 9.68 17.68 10.31 17.29 10.7 L 12.7 15.29 C 12.31 15.68 11.68 15.68 11.29 15.29 L 6.7 10.7 C 6.31 10.31 6.31 9.68 6.7 9.29 C 7.09 8.91 7.73 8.9 8.12 9.29 Z"
android:fillColor="#FF000000" />
</vector>
</aapt:attr>
<target android:name="down">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="pathData"
android:duration="250"
android:valueFrom="M 8.12 14.71 L 12 10.83 L 15.88 14.71 C 16.27 15.1 16.9 15.1 17.29 14.71 C 17.68 14.32 17.68 13.69 17.29 13.3 L 12.7 8.71 C 12.31 8.32 11.68 8.32 11.29 8.71 L 6.7 13.3 C 6.31 13.69 6.31 14.32 6.7 14.71 C 7.09 15.09 7.73 15.1 8.12 14.71 Z"
android:valueTo="M 8.12 9.29 L 12 13.17 L 15.88 9.29 C 16.27 8.9 16.9 8.9 17.29 9.29 C 17.68 9.68 17.68 10.31 17.29 10.7 L 12.7 15.29 C 12.31 15.68 11.68 15.68 11.29 15.29 L 6.7 10.7 C 6.31 10.31 6.31 9.68 6.7 9.29 C 7.09 8.91 7.73 8.9 8.12 9.29 Z"
android:valueType="pathType"
android:interpolator="@android:anim/accelerate_decelerate_interpolator" />
</aapt:attr>
</target>
</animated-vector>

View File

@ -0,0 +1,27 @@
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="vector"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:name="eye_visible"
android:pathData="M 12 4.5 C 7 4.5 2.73 7.61 1 12 C 2.73 16.39 7 19.5 12 19.5 C 17 19.5 21.27 16.39 23 12 C 21.27 7.61 17 4.5 12 4.5 Z M 12 17 C 9.24 17 7 14.76 7 12 C 7 9.24 9.24 7 12 7 C 14.76 7 17 9.24 17 12 C 17 14.76 14.76 17 12 17 Z M 12 9 C 10.34 9 9 10.34 9 12 C 9 13.66 10.34 15 12 15 C 13.66 15 15 13.66 15 12 C 15 10.34 13.66 9 12 9 Z"
android:fillColor="#FF000000" />
</vector>
</aapt:attr>
<target android:name="eye_visible">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="pathData"
android:duration="300"
android:valueFrom="M 12 4.5 C 7 4.5 2.73 7.61 1 12 C 2.73 16.39 7 19.5 12 19.5 C 17 19.5 21.27 16.39 23 12 C 21.27 7.61 17 4.5 12 4.5 Z M 12 17 C 9.24 17 7 14.76 7 12 C 7 9.24 9.24 7 12 7 C 14.76 7 17 9.24 17 12 C 17 14.76 14.76 17 12 17 Z M 14.411 6.703 C 12.751 6.703 11.411 8.043 11.411 9.703 C 11.411 11.363 12.751 12.703 14.411 12.703 C 16.071 12.703 17.411 11.363 17.411 9.703 C 17.411 8.043 16.071 6.703 14.411 6.703 Z"
android:valueTo="M 12 4.5 C 7 4.5 2.73 7.61 1 12 C 2.73 16.39 7 19.5 12 19.5 C 17 19.5 21.27 16.39 23 12 C 21.27 7.61 17 4.5 12 4.5 Z M 12 17 C 9.24 17 7 14.76 7 12 C 7 9.24 9.24 7 12 7 C 14.76 7 17 9.24 17 12 C 17 14.76 14.76 17 12 17 Z M 12 9 C 10.34 9 9 10.34 9 12 C 9 13.66 10.34 15 12 15 C 13.66 15 15 13.66 15 12 C 15 10.34 13.66 9 12 9 Z"
android:valueType="pathType"
android:interpolator="@android:interpolator/fast_out_slow_in" />
</aapt:attr>
</target>
</animated-vector>

View File

@ -0,0 +1,27 @@
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="vector"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:name="eye_visible"
android:pathData="M 12 4.5 C 7 4.5 2.73 7.61 1 12 C 2.73 16.39 7 19.5 12 19.5 C 17 19.5 21.27 16.39 23 12 C 21.27 7.61 17 4.5 12 4.5 Z M 12 17 C 9.24 17 7 14.76 7 12 C 7 9.24 9.24 7 12 7 C 14.76 7 17 9.24 17 12 C 17 14.76 14.76 17 12 17 Z M 12 9 C 10.34 9 9 10.34 9 12 C 9 13.66 10.34 15 12 15 C 13.66 15 15 13.66 15 12 C 15 10.34 13.66 9 12 9 Z"
android:fillColor="#FF000000" />
</vector>
</aapt:attr>
<target android:name="eye_visible">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="pathData"
android:duration="300"
android:valueFrom="M 12 4.5 C 7 4.5 2.73 7.61 1 12 C 2.73 16.39 7 19.5 12 19.5 C 17 19.5 21.27 16.39 23 12 C 21.27 7.61 17 4.5 12 4.5 Z M 12 17 C 9.24 17 7 14.76 7 12 C 7 9.24 9.24 7 12 7 C 14.76 7 17 9.24 17 12 C 17 14.76 14.76 17 12 17 Z M 12 9 C 10.34 9 9 10.34 9 12 C 9 13.66 10.34 15 12 15 C 13.66 15 15 13.66 15 12 C 15 10.34 13.66 9 12 9 Z"
android:valueTo="M 12 4.5 C 7 4.5 2.73 7.61 1 12 C 2.73 16.39 7 19.5 12 19.5 C 17 19.5 21.27 16.39 23 12 C 21.27 7.61 17 4.5 12 4.5 Z M 12 17 C 9.24 17 7 14.76 7 12 C 7 9.24 9.24 7 12 7 C 14.76 7 17 9.24 17 12 C 17 14.76 14.76 17 12 17 Z M 14.411 6.703 C 12.751 6.703 11.411 8.043 11.411 9.703 C 11.411 11.363 12.751 12.703 14.411 12.703 C 16.071 12.703 17.411 11.363 17.411 9.703 C 17.411 8.043 16.071 6.703 14.411 6.703 Z"
android:valueType="pathType"
android:interpolator="@android:interpolator/fast_out_slow_in" />
</aapt:attr>
</target>
</animated-vector>

View File

@ -1,8 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<animated-vector <animated-vector
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_sync_black_24dp"> android:drawable="@drawable/ic_sync_black_24dp"
>
<target <target
android:name="rotation" android:name="rotation"
android:animation="@animator/rotation_cw"/> android:animation="@animator/rotation_cw"
</animated-vector> />
</animated-vector>

View File

@ -1,5 +1,13 @@
<vector android:height="24dp" android:tint="#B4B4B4" <vector
android:viewportHeight="24.0" android:viewportWidth="24.0" android:height="24dp"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:tint="#B4B4B4"
<path android:fillColor="#FF000000" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,5c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM12,19.2c-2.5,0 -4.71,-1.28 -6,-3.22 0.03,-1.99 4,-3.08 6,-3.08 1.99,0 5.97,1.09 6,3.08 -1.29,1.94 -3.5,3.22 -6,3.22z"/> android:viewportHeight="24.0"
android:viewportWidth="24.0"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android"
>
<path
android:fillColor="#FF000000"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,5c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM12,19.2c-2.5,0 -4.71,-1.28 -6,-3.22 0.03,-1.99 4,-3.08 6,-3.08 1.99,0 5.97,1.09 6,3.08 -1.29,1.94 -3.5,3.22 -6,3.22z"
/>
</vector> </vector>

View File

@ -1,5 +1,13 @@
<vector android:height="24dp" android:tint="#FFFFFF" <vector
android:viewportHeight="24.0" android:viewportWidth="24.0" android:height="24dp"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:tint="#FFFFFF"
<path android:fillColor="#FF000000" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/> android:viewportHeight="24.0"
android:viewportWidth="24.0"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android"
>
<path
android:fillColor="#FF000000"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"
/>
</vector> </vector>

View File

@ -1,10 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24"
<path >
android:fillColor="#FF000000" <path
android:pathData="M12.72,2.03C6.63,1.6 1.6,6.63 2.03,12.72 2.39,18.01 7.01,22 12.31,22H16c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1h-3.67c-3.73,0 -7.15,-2.42 -8.08,-6.03 -1.49,-5.8 3.91,-11.21 9.71,-9.71C17.58,5.18 20,8.6 20,12.33v1.1c0,0.79 -0.71,1.57 -1.5,1.57s-1.5,-0.78 -1.5,-1.57v-1.25c0,-2.51 -1.78,-4.77 -4.26,-5.12 -3.4,-0.49 -6.27,2.45 -5.66,5.87 0.34,1.91 1.83,3.49 3.72,3.94 1.84,0.43 3.59,-0.16 4.74,-1.33 0.89,1.22 2.67,1.86 4.3,1.21 1.34,-0.53 2.16,-1.9 2.16,-3.34v-1.09c0,-5.31 -3.99,-9.93 -9.28,-10.29zM12,15c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3z" android:fillColor="#FF000000"
android:fillAlpha=".9"/> android:pathData="M12.72,2.03C6.63,1.6 1.6,6.63 2.03,12.72 2.39,18.01 7.01,22 12.31,22H16c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1h-3.67c-3.73,0 -7.15,-2.42 -8.08,-6.03 -1.49,-5.8 3.91,-11.21 9.71,-9.71C17.58,5.18 20,8.6 20,12.33v1.1c0,0.79 -0.71,1.57 -1.5,1.57s-1.5,-0.78 -1.5,-1.57v-1.25c0,-2.51 -1.78,-4.77 -4.26,-5.12 -3.4,-0.49 -6.27,2.45 -5.66,5.87 0.34,1.91 1.83,3.49 3.72,3.94 1.84,0.43 3.59,-0.16 4.74,-1.33 0.89,1.22 2.67,1.86 4.3,1.21 1.34,-0.53 2.16,-1.9 2.16,-3.34v-1.09c0,-5.31 -3.99,-9.93 -9.28,-10.29zM12,15c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3z"
android:fillAlpha=".9"
/>
</vector> </vector>

View File

@ -1,9 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24"
<path >
android:fillColor="#FFF" <path
android:pathData="M19,11H7.83l4.88,-4.88c0.39,-0.39 0.39,-1.03 0,-1.42 -0.39,-0.39 -1.02,-0.39 -1.41,0l-6.59,6.59c-0.39,0.39 -0.39,1.02 0,1.41l6.59,6.59c0.39,0.39 1.02,0.39 1.41,0 0.39,-0.39 0.39,-1.02 0,-1.41L7.83,13H19c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1z"/> android:fillColor="#FFF"
android:pathData="M19,11H7.83l4.88,-4.88c0.39,-0.39 0.39,-1.03 0,-1.42 -0.39,-0.39 -1.02,-0.39 -1.41,0l-6.59,6.59c-0.39,0.39 -0.39,1.02 0,1.41l6.59,6.59c0.39,0.39 1.02,0.39 1.41,0 0.39,-0.39 0.39,-1.02 0,-1.41L7.83,13H19c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1z"
/>
</vector> </vector>

View File

@ -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>

View File

@ -1,9 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24"
<path >
android:fillColor="#FFF" <path
android:pathData="M5,13h11.17l-4.88,4.88c-0.39,0.39 -0.39,1.03 0,1.42 0.39,0.39 1.02,0.39 1.41,0l6.59,-6.59c0.39,-0.39 0.39,-1.02 0,-1.41l-6.58,-6.6c-0.39,-0.39 -1.02,-0.39 -1.41,0 -0.39,0.39 -0.39,1.02 0,1.41L16.17,11H5c-0.55,0 -1,0.45 -1,1s0.45,1 1,1z"/> android:fillColor="#FFF"
android:pathData="M5,13h11.17l-4.88,4.88c-0.39,0.39 -0.39,1.03 0,1.42 0.39,0.39 1.02,0.39 1.41,0l6.59,-6.59c0.39,-0.39 0.39,-1.02 0,-1.41l-6.58,-6.6c-0.39,-0.39 -1.02,-0.39 -1.41,0 -0.39,0.39 -0.39,1.02 0,1.41L16.17,11H5c-0.55,0 -1,0.45 -1,1s0.45,1 1,1z"
/>
</vector> </vector>

View File

@ -1,9 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24"
<path >
android:fillColor="#FFFFFF" <path
android:pathData="M12,6v1.79c0,0.45 0.54,0.67 0.85,0.35l2.79,-2.79c0.2,-0.2 0.2,-0.51 0,-0.71l-2.79,-2.79c-0.31,-0.31 -0.85,-0.09 -0.85,0.36L12,4c-4.42,0 -8,3.58 -8,8 0,1.04 0.2,2.04 0.57,2.95 0.27,0.67 1.13,0.85 1.64,0.34 0.27,-0.27 0.38,-0.68 0.23,-1.04C6.15,13.56 6,12.79 6,12c0,-3.31 2.69,-6 6,-6zM17.79,8.71c-0.27,0.27 -0.38,0.69 -0.23,1.04 0.28,0.7 0.44,1.46 0.44,2.25 0,3.31 -2.69,6 -6,6v-1.79c0,-0.45 -0.54,-0.67 -0.85,-0.35l-2.79,2.79c-0.2,0.2 -0.2,0.51 0,0.71l2.79,2.79c0.31,0.31 0.85,0.09 0.85,-0.35L12,20c4.42,0 8,-3.58 8,-8 0,-1.04 -0.2,-2.04 -0.57,-2.95 -0.27,-0.67 -1.13,-0.85 -1.64,-0.34z"/> android:fillColor="#FFFFFF"
android:pathData="M12,6v1.79c0,0.45 0.54,0.67 0.85,0.35l2.79,-2.79c0.2,-0.2 0.2,-0.51 0,-0.71l-2.79,-2.79c-0.31,-0.31 -0.85,-0.09 -0.85,0.36L12,4c-4.42,0 -8,3.58 -8,8 0,1.04 0.2,2.04 0.57,2.95 0.27,0.67 1.13,0.85 1.64,0.34 0.27,-0.27 0.38,-0.68 0.23,-1.04C6.15,13.56 6,12.79 6,12c0,-3.31 2.69,-6 6,-6zM17.79,8.71c-0.27,0.27 -0.38,0.69 -0.23,1.04 0.28,0.7 0.44,1.46 0.44,2.25 0,3.31 -2.69,6 -6,6v-1.79c0,-0.45 -0.54,-0.67 -0.85,-0.35l-2.79,2.79c-0.2,0.2 -0.2,0.51 0,0.71l2.79,2.79c0.31,0.31 0.85,0.09 0.85,-0.35L12,20c4.42,0 8,-3.58 8,-8 0,-1.04 -0.2,-2.04 -0.57,-2.95 -0.27,-0.67 -1.13,-0.85 -1.64,-0.34z"
/>
</vector> </vector>

View File

@ -1,13 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp" android:width="100dp"
android:height="20dp" android:height="20dp"
android:viewportWidth="100" android:viewportWidth="100"
android:viewportHeight="20"> android:viewportHeight="20"
<path >
android:pathData="M21.184,20C21.541,19.87 21.904,19.736 22.272,19.598C22.635,19.463 23.871,19 24.04,18.937C33.64,15.348 39.647,14 50,14C60.271,14 65.362,15.222 74.629,18.928C75.584,19.311 76.498,19.668 77.379,20L83.604,20C81.093,19.269 78.465,18.309 75.371,17.072C65.888,13.278 60.562,12 50,12C39.374,12 33.145,13.397 23.34,17.063C23.169,17.127 21.934,17.59 21.573,17.725C19.098,18.648 16.913,19.399 14.849,20L21.184,20L21.184,20ZM21.184,0C13.258,2.892 8.077,4 0,4L0,4L0,2C5.744,2 9.951,1.426 14.849,0L21.184,0L21.184,0ZM77.379,0C85.239,2.966 90.502,4 100,4L100,2C93.158,2 88.614,1.458 83.604,0L77.379,0L77.379,0ZM0,14C8.441,14 13.718,12.79 22.272,9.598C22.635,9.463 23.871,9 24.04,8.937C33.64,5.348 39.647,4 50,4C60.271,4 65.362,5.222 74.629,8.928C84.112,12.722 89.438,14 100,14L100,12C89.729,12 84.638,10.778 75.371,7.072C65.888,3.278 60.562,2 50,2C39.374,2 33.145,3.397 23.34,7.063C23.169,7.127 21.934,7.59 21.573,7.725C13.224,10.84 8.164,12 0,12L0,14L0,14L0,14Z" <path
android:strokeWidth="1" android:pathData="M21.184,20C21.541,19.87 21.904,19.736 22.272,19.598C22.635,19.463 23.871,19 24.04,18.937C33.64,15.348 39.647,14 50,14C60.271,14 65.362,15.222 74.629,18.928C75.584,19.311 76.498,19.668 77.379,20L83.604,20C81.093,19.269 78.465,18.309 75.371,17.072C65.888,13.278 60.562,12 50,12C39.374,12 33.145,13.397 23.34,17.063C23.169,17.127 21.934,17.59 21.573,17.725C19.098,18.648 16.913,19.399 14.849,20L21.184,20L21.184,20ZM21.184,0C13.258,2.892 8.077,4 0,4L0,4L0,2C5.744,2 9.951,1.426 14.849,0L21.184,0L21.184,0ZM77.379,0C85.239,2.966 90.502,4 100,4L100,2C93.158,2 88.614,1.458 83.604,0L77.379,0L77.379,0ZM0,14C8.441,14 13.718,12.79 22.272,9.598C22.635,9.463 23.871,9 24.04,8.937C33.64,5.348 39.647,4 50,4C60.271,4 65.362,5.222 74.629,8.928C84.112,12.722 89.438,14 100,14L100,12C89.729,12 84.638,10.778 75.371,7.072C65.888,3.278 60.562,2 50,2C39.374,2 33.145,3.397 23.34,7.063C23.169,7.127 21.934,7.59 21.573,7.725C13.224,10.84 8.164,12 0,12L0,14L0,14L0,14Z"
android:fillColor="@color/colorAccent" android:strokeWidth="1"
android:fillAlpha="0.5" android:fillColor="@color/colorAccent"
android:fillType="evenOdd" android:fillAlpha="0.5"
android:strokeColor="#00000000"/> android:fillType="evenOdd"
android:strokeColor="#00000000"
/>
</vector> </vector>

View File

@ -1,7 +1,17 @@
<vector android:height="100dp" android:viewportHeight="100.00009" <vector
android:viewportWidth="100.00009" android:width="100dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:height="100dp"
<path android:fillAlpha="1" android:fillColor="#ffffff" android:viewportHeight="100.00009"
android:viewportWidth="100.00009"
android:width="100dp"
xmlns:android="http://schemas.android.com/apk/res/android"
>
<path
android:fillAlpha="1"
android:fillColor="#ffffff"
android:fillType="nonZero" android:fillType="nonZero"
android:pathData="m0,0v4.233c0,-2.345 1.888,-4.233 4.233,-4.233zM95.767,0c2.345,0 4.233,1.888 4.233,4.233L100,0ZM0,95.767v4.233h4.233c-2.345,0 -4.233,-1.888 -4.233,-4.233zM100,95.767c0,2.345 -1.888,4.233 -4.233,4.233h4.233z" android:pathData="m0,0v4.233c0,-2.345 1.888,-4.233 4.233,-4.233zM95.767,0c2.345,0 4.233,1.888 4.233,4.233L100,0ZM0,95.767v4.233h4.233c-2.345,0 -4.233,-1.888 -4.233,-4.233zM100,95.767c0,2.345 -1.888,4.233 -4.233,4.233h4.233z"
android:strokeAlpha="1" android:strokeColor="#00000000" android:strokeWidth="1"/> android:strokeAlpha="1"
android:strokeColor="#00000000"
android:strokeWidth="1"
/>
</vector> </vector>

View File

@ -1,9 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24"
<path >
android:fillColor="#FFF" <path
android:pathData="M9,16.17L5.53,12.7c-0.39,-0.39 -1.02,-0.39 -1.41,0 -0.39,0.39 -0.39,1.02 0,1.41l4.18,4.18c0.39,0.39 1.02,0.39 1.41,0L20.29,7.71c0.39,-0.39 0.39,-1.02 0,-1.41 -0.39,-0.39 -1.02,-0.39 -1.41,0L9,16.17z"/> android:fillColor="#FFF"
android:pathData="M9,16.17L5.53,12.7c-0.39,-0.39 -1.02,-0.39 -1.41,0 -0.39,0.39 -0.39,1.02 0,1.41l4.18,4.18c0.39,0.39 1.02,0.39 1.41,0L20.29,7.71c0.39,-0.39 0.39,-1.02 0,-1.41 -0.39,-0.39 -1.02,-0.39 -1.41,0L9,16.17z"
/>
</vector> </vector>

View File

@ -1,9 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24"
<path >
android:fillColor="#FFF" <path
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM15.88,8.29L10,14.17l-1.88,-1.88c-0.39,-0.39 -1.02,-0.39 -1.41,0 -0.39,0.39 -0.39,1.02 0,1.41l2.59,2.59c0.39,0.39 1.02,0.39 1.41,0L17.3,9.7c0.39,-0.39 0.39,-1.02 0,-1.41 -0.39,-0.39 -1.03,-0.39 -1.42,0z"/> android:fillColor="#FFF"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM15.88,8.29L10,14.17l-1.88,-1.88c-0.39,-0.39 -1.02,-0.39 -1.41,0 -0.39,0.39 -0.39,1.02 0,1.41l2.59,2.59c0.39,0.39 1.02,0.39 1.41,0L17.3,9.7c0.39,-0.39 0.39,-1.02 0,-1.41 -0.39,-0.39 -1.03,-0.39 -1.42,0z"
/>
</vector> </vector>

File diff suppressed because one or more lines are too long

View File

@ -1,9 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24"
<path >
android:fillColor="#FFF" <path
android:pathData="M18.3,5.71c-0.39,-0.39 -1.02,-0.39 -1.41,0L12,10.59 7.11,5.7c-0.39,-0.39 -1.02,-0.39 -1.41,0 -0.39,0.39 -0.39,1.02 0,1.41L10.59,12 5.7,16.89c-0.39,0.39 -0.39,1.02 0,1.41 0.39,0.39 1.02,0.39 1.41,0L12,13.41l4.89,4.89c0.39,0.39 1.02,0.39 1.41,0 0.39,-0.39 0.39,-1.02 0,-1.41L13.41,12l4.89,-4.89c0.38,-0.38 0.38,-1.02 0,-1.4z"/> android:fillColor="#FFF"
android:pathData="M18.3,5.71c-0.39,-0.39 -1.02,-0.39 -1.41,0L12,10.59 7.11,5.7c-0.39,-0.39 -1.02,-0.39 -1.41,0 -0.39,0.39 -0.39,1.02 0,1.41L10.59,12 5.7,16.89c-0.39,0.39 -0.39,1.02 0,1.41 0.39,0.39 1.02,0.39 1.41,0L12,13.41l4.89,4.89c0.39,0.39 1.02,0.39 1.41,0 0.39,-0.39 0.39,-1.02 0,-1.41L13.41,12l4.89,-4.89c0.38,-0.38 0.38,-1.02 0,-1.4z"
/>
</vector> </vector>

View File

@ -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>

View File

@ -1,5 +1,13 @@
<vector android:height="24dp" android:tint="#FFFFFF" <vector
android:viewportHeight="24.0" android:viewportWidth="24.0" android:height="24dp"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:tint="#FFFFFF"
<path android:fillColor="#FFF" android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM14,13v4h-4v-4H7l5,-5 5,5h-3z"/> android:viewportHeight="24.0"
android:viewportWidth="24.0"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android"
>
<path
android:fillColor="#FFF"
android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM14,13v4h-4v-4H7l5,-5 5,5h-3z"
/>
</vector> </vector>

View File

@ -1,5 +1,13 @@
<vector android:height="24dp" android:tint="#FFFFFF" <vector
android:viewportHeight="24.0" android:viewportWidth="24.0" android:height="24dp"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:tint="#FFFFFF"
<path android:fillColor="#FF000000" android:pathData="M9.4,16.6L4.8,12l4.6,-4.6L8,6l-6,6 6,6 1.4,-1.4zM14.6,16.6l4.6,-4.6 -4.6,-4.6L16,6l6,6 -6,6 -1.4,-1.4z"/> android:viewportHeight="24.0"
android:viewportWidth="24.0"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android"
>
<path
android:fillColor="#FF000000"
android:pathData="M9.4,16.6L4.8,12l4.6,-4.6L8,6l-6,6 6,6 1.4,-1.4zM14.6,16.6l4.6,-4.6 -4.6,-4.6L16,6l6,6 -6,6 -1.4,-1.4z"
/>
</vector> </vector>

View File

@ -1,9 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24"
<path >
android:fillColor="#FF000000" <path
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2L18,9c0,-1.1 -0.9,-2 -2,-2L8,7c-1.1,0 -2,0.9 -2,2v10zM9.17,11.17c0.39,-0.39 1.02,-0.39 1.41,0L12,12.59l1.42,-1.42c0.39,-0.39 1.02,-0.39 1.41,0 0.39,0.39 0.39,1.02 0,1.41L13.41,14l1.42,1.42c0.39,0.39 0.39,1.02 0,1.41 -0.39,0.39 -1.02,0.39 -1.41,0L12,15.41l-1.42,1.42c-0.39,0.39 -1.02,0.39 -1.41,0 -0.39,-0.39 -0.39,-1.02 0,-1.41L10.59,14l-1.42,-1.42c-0.39,-0.38 -0.39,-1.02 0,-1.41zM15.5,4l-0.71,-0.71c-0.18,-0.18 -0.44,-0.29 -0.7,-0.29L9.91,3c-0.26,0 -0.52,0.11 -0.7,0.29L8.5,4L6,4c-0.55,0 -1,0.45 -1,1s0.45,1 1,1h12c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1h-2.5z"/> android:fillColor="#FF000000"
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2L18,9c0,-1.1 -0.9,-2 -2,-2L8,7c-1.1,0 -2,0.9 -2,2v10zM9.17,11.17c0.39,-0.39 1.02,-0.39 1.41,0L12,12.59l1.42,-1.42c0.39,-0.39 1.02,-0.39 1.41,0 0.39,0.39 0.39,1.02 0,1.41L13.41,14l1.42,1.42c0.39,0.39 0.39,1.02 0,1.41 -0.39,0.39 -1.02,0.39 -1.41,0L12,15.41l-1.42,1.42c-0.39,0.39 -1.02,0.39 -1.41,0 -0.39,-0.39 -0.39,-1.02 0,-1.41L10.59,14l-1.42,-1.42c-0.39,-0.38 -0.39,-1.02 0,-1.41zM15.5,4l-0.71,-0.71c-0.18,-0.18 -0.44,-0.29 -0.7,-0.29L9.91,3c-0.26,0 -0.52,0.11 -0.7,0.29L8.5,4L6,4c-0.55,0 -1,0.45 -1,1s0.45,1 1,1h12c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1h-2.5z"
/>
</vector> </vector>

View File

@ -1,5 +1,13 @@
<vector android:height="24dp" android:tint="#FFFFFF" <vector
android:viewportHeight="24.0" android:viewportWidth="24.0" android:height="24dp"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:tint="#FFFFFF"
<path android:fillColor="#FF000000" android:pathData="M14,2L6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6zM16,18L8,18v-2h8v2zM16,14L8,14v-2h8v2zM13,9L13,3.5L18.5,9L13,9z"/> android:viewportHeight="24.0"
android:viewportWidth="24.0"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android"
>
<path
android:fillColor="#FF000000"
android:pathData="M14,2L6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6zM16,18L8,18v-2h8v2zM16,14L8,14v-2h8v2zM13,9L13,3.5L18.5,9L13,9z"
/>
</vector> </vector>

View File

@ -1,5 +1,13 @@
<vector android:height="24dp" android:tint="#FFFFFF" <vector
android:viewportHeight="24.0" android:viewportWidth="24.0" android:height="24dp"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:tint="#FFFFFF"
<path android:fillColor="#FF000000" android:pathData="M12,7L12,3L2,3v18h20L22,7L12,7zM6,19L4,19v-2h2v2zM6,15L4,15v-2h2v2zM6,11L4,11L4,9h2v2zM6,7L4,7L4,5h2v2zM10,19L8,19v-2h2v2zM10,15L8,15v-2h2v2zM10,11L8,11L8,9h2v2zM10,7L8,7L8,5h2v2zM20,19h-8v-2h2v-2h-2v-2h2v-2h-2L12,9h8v10zM18,11h-2v2h2v-2zM18,15h-2v2h2v-2z"/> android:viewportHeight="24.0"
android:viewportWidth="24.0"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android"
>
<path
android:fillColor="#FF000000"
android:pathData="M12,7L12,3L2,3v18h20L22,7L12,7zM6,19L4,19v-2h2v2zM6,15L4,15v-2h2v2zM6,11L4,11L4,9h2v2zM6,7L4,7L4,5h2v2zM10,19L8,19v-2h2v2zM10,15L8,15v-2h2v2zM10,11L8,11L8,9h2v2zM10,7L8,7L8,5h2v2zM20,19h-8v-2h2v-2h-2v-2h2v-2h-2L12,9h8v10zM18,11h-2v2h2v-2zM18,15h-2v2h2v-2z"
/>
</vector> </vector>

View File

@ -1,9 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24"
<path >
android:fillColor="#FF000000" <path
android:pathData="M20,4L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM19.6,8.25l-7.07,4.42c-0.32,0.2 -0.74,0.2 -1.06,0L4.4,8.25c-0.25,-0.16 -0.4,-0.43 -0.4,-0.72 0,-0.67 0.73,-1.07 1.3,-0.72L12,11l6.7,-4.19c0.57,-0.35 1.3,0.05 1.3,0.72 0,0.29 -0.15,0.56 -0.4,0.72z"/> android:fillColor="#FF000000"
android:pathData="M20,4L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM19.6,8.25l-7.07,4.42c-0.32,0.2 -0.74,0.2 -1.06,0L4.4,8.25c-0.25,-0.16 -0.4,-0.43 -0.4,-0.72 0,-0.67 0.73,-1.07 1.3,-0.72L12,11l6.7,-4.19c0.57,-0.35 1.3,0.05 1.3,0.72 0,0.29 -0.15,0.56 -0.4,0.72z"
/>
</vector> </vector>

View File

@ -1,9 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24"
<path >
android:fillColor="#FFF" <path
android:pathData="M12,7c0.55,0 1,0.45 1,1v4c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1L11,8c0,-0.55 0.45,-1 1,-1zM11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM13,17h-2v-2h2v2z"/> android:fillColor="#FFF"
android:pathData="M12,7c0.55,0 1,0.45 1,1v4c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1L11,8c0,-0.55 0.45,-1 1,-1zM11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM13,17h-2v-2h2v2z"
/>
</vector> </vector>

View File

@ -1,9 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24"
<path >
android:fillColor="#FF000000" <path
android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/> android:fillColor="#FF000000"
android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"
/>
</vector> </vector>

View File

@ -1,9 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24"
<path >
android:fillColor="#FF000000" <path
android:pathData="M15,1L4,1c-1.1,0 -2,0.9 -2,2v13c0,0.55 0.45,1 1,1s1,-0.45 1,-1L4,4c0,-0.55 0.45,-1 1,-1h10c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1zM15.59,5.59l4.83,4.83c0.37,0.37 0.58,0.88 0.58,1.41L21,21c0,1.1 -0.9,2 -2,2L7.99,23C6.89,23 6,22.1 6,21l0.01,-14c0,-1.1 0.89,-2 1.99,-2h6.17c0.53,0 1.04,0.21 1.42,0.59zM15,12h4.5L14,6.5L14,11c0,0.55 0.45,1 1,1z"/> android:fillColor="#FF000000"
android:pathData="M15,1L4,1c-1.1,0 -2,0.9 -2,2v13c0,0.55 0.45,1 1,1s1,-0.45 1,-1L4,4c0,-0.55 0.45,-1 1,-1h10c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1zM15.59,5.59l4.83,4.83c0.37,0.37 0.58,0.88 0.58,1.41L21,21c0,1.1 -0.9,2 -2,2L7.99,23C6.89,23 6,22.1 6,21l0.01,-14c0,-1.1 0.89,-2 1.99,-2h6.17c0.53,0 1.04,0.21 1.42,0.59zM15,12h4.5L14,6.5L14,11c0,0.55 0.45,1 1,1z"
/>
</vector> </vector>

View File

@ -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="M12,2A10,10 0,0 0,2 12C2,16.42 4.87,20.17 8.84,21.5C9.34,21.58 9.5,21.27 9.5,21C9.5,20.77 9.5,20.14 9.5,19.31C6.73,19.91 6.14,17.97 6.14,17.97C5.68,16.81 5.03,16.5 5.03,16.5C4.12,15.88 5.1,15.9 5.1,15.9C6.1,15.97 6.63,16.93 6.63,16.93C7.5,18.45 8.97,18 9.54,17.76C9.63,17.11 9.89,16.67 10.17,16.42C7.95,16.17 5.62,15.31 5.62,11.5C5.62,10.39 6,9.5 6.65,8.79C6.55,8.54 6.2,7.5 6.75,6.15C6.75,6.15 7.59,5.88 9.5,7.17C10.29,6.95 11.15,6.84 12,6.84C12.85,6.84 13.71,6.95 14.5,7.17C16.41,5.88 17.25,6.15 17.25,6.15C17.8,7.5 17.45,8.54 17.35,8.79C18,9.5 18.38,10.39 18.38,11.5C18.38,15.32 16.04,16.16 13.81,16.41C14.17,16.72 14.5,17.33 14.5,18.26C14.5,19.6 14.5,20.68 14.5,21C14.5,21.27 14.66,21.59 15.17,21.5C19.14,20.16 22,16.42 22,12A10,10 0,0 0,12 2Z"/>
</vector>

View File

@ -1,9 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24"
<path >
android:fillColor="#FFF" <path
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,16h2v2h-2zM12.61,6.04c-2.06,-0.3 -3.88,0.97 -4.43,2.79 -0.18,0.58 0.26,1.17 0.87,1.17h0.2c0.41,0 0.74,-0.29 0.88,-0.67 0.32,-0.89 1.27,-1.5 2.3,-1.28 0.95,0.2 1.65,1.13 1.57,2.1 -0.1,1.34 -1.62,1.63 -2.45,2.88 0,0.01 -0.01,0.01 -0.01,0.02 -0.01,0.02 -0.02,0.03 -0.03,0.05 -0.09,0.15 -0.18,0.32 -0.25,0.5 -0.01,0.03 -0.03,0.05 -0.04,0.08 -0.01,0.02 -0.01,0.04 -0.02,0.07 -0.12,0.34 -0.2,0.75 -0.2,1.25h2c0,-0.42 0.11,-0.77 0.28,-1.07 0.02,-0.03 0.03,-0.06 0.05,-0.09 0.08,-0.14 0.18,-0.27 0.28,-0.39 0.01,-0.01 0.02,-0.03 0.03,-0.04 0.1,-0.12 0.21,-0.23 0.33,-0.34 0.96,-0.91 2.26,-1.65 1.99,-3.56 -0.24,-1.74 -1.61,-3.21 -3.35,-3.47z"/> android:fillColor="#FFF"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,16h2v2h-2zM12.61,6.04c-2.06,-0.3 -3.88,0.97 -4.43,2.79 -0.18,0.58 0.26,1.17 0.87,1.17h0.2c0.41,0 0.74,-0.29 0.88,-0.67 0.32,-0.89 1.27,-1.5 2.3,-1.28 0.95,0.2 1.65,1.13 1.57,2.1 -0.1,1.34 -1.62,1.63 -2.45,2.88 0,0.01 -0.01,0.01 -0.01,0.02 -0.01,0.02 -0.02,0.03 -0.03,0.05 -0.09,0.15 -0.18,0.32 -0.25,0.5 -0.01,0.03 -0.03,0.05 -0.04,0.08 -0.01,0.02 -0.01,0.04 -0.02,0.07 -0.12,0.34 -0.2,0.75 -0.2,1.25h2c0,-0.42 0.11,-0.77 0.28,-1.07 0.02,-0.03 0.03,-0.06 0.05,-0.09 0.08,-0.14 0.18,-0.27 0.28,-0.39 0.01,-0.01 0.02,-0.03 0.03,-0.04 0.1,-0.12 0.21,-0.23 0.33,-0.34 0.96,-0.91 2.26,-1.65 1.99,-3.56 -0.24,-1.74 -1.61,-3.21 -3.35,-3.47z"
/>
</vector> </vector>

View File

@ -0,0 +1,12 @@
<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="#000"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,16h2v2h-2zM12.61,6.04c-2.06,-0.3 -3.88,0.97 -4.43,2.79 -0.18,0.58 0.26,1.17 0.87,1.17h0.2c0.41,0 0.74,-0.29 0.88,-0.67 0.32,-0.89 1.27,-1.5 2.3,-1.28 0.95,0.2 1.65,1.13 1.57,2.1 -0.1,1.34 -1.62,1.63 -2.45,2.88 0,0.01 -0.01,0.01 -0.01,0.02 -0.01,0.02 -0.02,0.03 -0.03,0.05 -0.09,0.15 -0.18,0.32 -0.25,0.5 -0.01,0.03 -0.03,0.05 -0.04,0.08 -0.01,0.02 -0.01,0.04 -0.02,0.07 -0.12,0.34 -0.2,0.75 -0.2,1.25h2c0,-0.42 0.11,-0.77 0.28,-1.07 0.02,-0.03 0.03,-0.06 0.05,-0.09 0.08,-0.14 0.18,-0.27 0.28,-0.39 0.01,-0.01 0.02,-0.03 0.03,-0.04 0.1,-0.12 0.21,-0.23 0.33,-0.34 0.96,-0.91 2.26,-1.65 1.99,-3.56 -0.24,-1.74 -1.61,-3.21 -3.35,-3.47z"
/>
</vector>

View File

@ -1,5 +1,13 @@
<vector android:height="24dp" android:tint="#FFFFFF" <vector
android:viewportHeight="24.0" android:viewportWidth="24.0" android:height="24dp"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:tint="#FFFFFF"
<path android:fillColor="#FFF" 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"/> android:viewportHeight="24.0"
android:viewportWidth="24.0"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android"
>
<path
android:fillColor="#FFF"
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> </vector>

View File

@ -0,0 +1,12 @@
<vector
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android"
>
<path
android:fillColor="#000"
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>

View File

@ -1,9 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24"
<path >
android:fillColor="#FF000000" <path
android:pathData="M12.65,10C11.7,7.31 8.9,5.5 5.77,6.12c-2.29,0.46 -4.15,2.29 -4.63,4.58C0.32,14.57 3.26,18 7,18c2.61,0 4.83,-1.67 5.65,-4H17v2c0,1.1 0.9,2 2,2s2,-0.9 2,-2v-2c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2h-8.35zM7,14c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z"/> android:fillColor="#FF000000"
android:pathData="M12.65,10C11.7,7.31 8.9,5.5 5.77,6.12c-2.29,0.46 -4.15,2.29 -4.63,4.58C0.32,14.57 3.26,18 7,18c2.61,0 4.83,-1.67 5.65,-4H17v2c0,1.1 0.9,2 2,2s2,-0.9 2,-2v-2c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2h-8.35zM7,14c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z"
/>
</vector> </vector>

Some files were not shown because too many files have changed in this diff Show More