| @@ -5,7 +5,7 @@ | |||
| </component> | |||
| <component name="NullableNotNullManager"> | |||
| <option name="myDefaultNullable" value="org.jetbrains.annotations.Nullable" /> | |||
| <option name="myDefaultNotNull" value="android.support.annotation.NonNull" /> | |||
| <option name="myDefaultNotNull" value="androidx.annotation.RecentlyNonNull" /> | |||
| <option name="myNullables"> | |||
| <value> | |||
| <list size="10"> | |||
| @@ -38,7 +38,7 @@ | |||
| </value> | |||
| </option> | |||
| </component> | |||
| <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK"> | |||
| <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK"> | |||
| <output url="file://$PROJECT_DIR$/build/classes" /> | |||
| </component> | |||
| <component name="ProjectType"> | |||
| @@ -1,3 +1,17 @@ | |||
| # MISPBump | |||
| # What does this app? | |||
| 1. Exchange public keys to make following communication private (via QR code) | |||
| + Diffie Hellman key exchange | |||
| 2. Exchange information needed to sync two MISP instances | |||
| 3. Upload information to MISP instance. | |||
| # Problems | |||
| + Androidx migration - bug in material design dependency (just for editor) | |||
| + Loading of self signed certificates (currently not supported) | |||
| # TODOs | |||
| + custom password for syncUser | |||
| + upload ack screen | |||
| + translation | |||
| # MISPBump | |||
|  | |||
| @@ -10,7 +10,7 @@ android { | |||
| targetSdkVersion 28 | |||
| versionCode 1 | |||
| versionName "1.0" | |||
| testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" | |||
| testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | |||
| vectorDrawables.useSupportLibrary = true | |||
| } | |||
| buildTypes { | |||
| @@ -22,35 +22,30 @@ android { | |||
| } | |||
| dependencies { | |||
| // android | |||
| implementation 'com.google.android.material:material:1.0.0' | |||
| implementation 'androidx.legacy:legacy-support-v4:1.0.0' | |||
| implementation 'androidx.appcompat:appcompat:1.1.0-beta01' | |||
| implementation 'androidx.constraintlayout:constraintlayout:1.1.3' | |||
| implementation 'androidx.cardview:cardview:1.0.0' | |||
| implementation 'androidx.recyclerview:recyclerview:1.0.0' | |||
| // retrofit | |||
| implementation 'com.squareup.retrofit2:retrofit:2.5.0' | |||
| implementation 'com.squareup.retrofit2:retrofit:2.6.0' | |||
| implementation 'com.squareup.retrofit2:converter-gson:2.5.0' | |||
| implementation 'com.squareup.okhttp3:logging-interceptor:3.8.0' | |||
| // android | |||
| implementation 'com.android.support:support-v4:28.0.0' | |||
| implementation 'com.android.support:design:28.0.0' | |||
| implementation 'com.android.support:appcompat-v7:28.0.0' | |||
| implementation 'com.android.support.constraint:constraint-layout:1.1.3' | |||
| implementation 'com.android.support:cardview-v7:28.0.0' | |||
| implementation 'com.android.support:recyclerview-v7:28.0.0' | |||
| implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0' | |||
| // barcode scanning | |||
| // barcode reading | |||
| implementation 'com.google.android.gms:play-services-vision:17.0.2' | |||
| // barcode generation | |||
| implementation 'com.journeyapps:zxing-android-embedded:3.2.0@aar' | |||
| implementation 'com.google.zxing:core:3.3.0' | |||
| // override due to play-services-vision version conflicts | |||
| implementation 'com.android.support:support-media-compat:28.0.0' | |||
| implementation 'com.android.support:support-v4:28.0.0' | |||
| implementation 'com.google.zxing:core:3.4.0' | |||
| implementation fileTree(dir: 'libs', include: ['*.jar']) | |||
| testImplementation 'junit:junit:4.12' | |||
| androidTestImplementation 'com.android.support.test:runner:1.0.2' | |||
| androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' | |||
| androidTestImplementation 'androidx.test:runner:1.2.0' | |||
| androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' | |||
| implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" | |||
| } | |||
| repositories { | |||
| @@ -1,8 +1,8 @@ | |||
| package lu.circl.mispbump; | |||
| import android.content.Context; | |||
| import android.support.test.InstrumentationRegistry; | |||
| import android.support.test.runner.AndroidJUnit4; | |||
| import androidx.test.InstrumentationRegistry; | |||
| import androidx.test.runner.AndroidJUnit4; | |||
| import org.junit.Test; | |||
| import org.junit.runner.RunWith; | |||
| @@ -15,6 +15,10 @@ | |||
| android:supportsRtl="true" | |||
| android:theme="@style/AppTheme" | |||
| tools:ignore="GoogleAppIndexingWarning"> | |||
| <activity android:name=".activities.UploadActivity" | |||
| android:configChanges="orientation|screenSize" | |||
| android:label="Upload"/> | |||
| <activity android:name=".activities.PreferenceActivity" /> | |||
| <activity android:name=".activities.StartUpActivity"> | |||
| <intent-filter> | |||
| <action android:name="android.intent.action.MAIN" /> | |||
| @@ -30,14 +34,14 @@ | |||
| android:label="@string/login" /> | |||
| <activity | |||
| android:name=".activities.SyncActivity" | |||
| android:configChanges="orientation|screenSize" | |||
| android:label="@string/sync" | |||
| android:parentActivityName=".activities.HomeActivity" /> | |||
| <activity | |||
| android:name=".activities.ProfileActivity" | |||
| android:label="Profile" | |||
| android:theme="@style/AppTheme.Translucent" | |||
| android:parentActivityName=".activities.HomeActivity"/> | |||
| android:parentActivityName=".activities.HomeActivity" | |||
| android:theme="@style/AppTheme.Translucent" /> | |||
| </application> | |||
| </manifest> | |||
| @@ -2,27 +2,30 @@ package lu.circl.mispbump.activities; | |||
| import android.content.Intent; | |||
| import android.os.Bundle; | |||
| import android.support.design.widget.CoordinatorLayout; | |||
| import android.support.design.widget.FloatingActionButton; | |||
| import android.support.v7.app.ActionBar; | |||
| import android.support.v7.app.AppCompatActivity; | |||
| import android.support.v7.widget.LinearLayoutManager; | |||
| import android.support.v7.widget.RecyclerView; | |||
| import android.support.v7.widget.Toolbar; | |||
| import android.util.Log; | |||
| import androidx.coordinatorlayout.widget.CoordinatorLayout; | |||
| import com.google.android.material.floatingactionbutton.FloatingActionButton; | |||
| import com.google.android.material.snackbar.Snackbar; | |||
| import com.google.gson.Gson; | |||
| import androidx.appcompat.app.ActionBar; | |||
| import androidx.appcompat.app.AppCompatActivity; | |||
| import androidx.recyclerview.widget.LinearLayoutManager; | |||
| import androidx.recyclerview.widget.RecyclerView; | |||
| import androidx.appcompat.widget.Toolbar; | |||
| import android.view.Menu; | |||
| import android.view.MenuItem; | |||
| import android.view.View; | |||
| import android.widget.TextView; | |||
| import android.widget.Toast; | |||
| import java.util.List; | |||
| import lu.circl.mispbump.R; | |||
| import lu.circl.mispbump.adapters.SyncAdapter; | |||
| import lu.circl.mispbump.auxiliary.DialogManager; | |||
| import lu.circl.mispbump.auxiliary.PreferenceManager; | |||
| import lu.circl.mispbump.interfaces.IOnItemClickListener; | |||
| import lu.circl.mispbump.models.UploadInformation; | |||
| import lu.circl.mispbump.restful_client.MispRestClient; | |||
| public class HomeActivity extends AppCompatActivity { | |||
| @@ -54,7 +57,8 @@ public class HomeActivity extends AppCompatActivity { | |||
| @Override | |||
| protected void onResume() { | |||
| super.onResume(); | |||
| populateRecyclerView(); | |||
| initializeRecyclerView(); | |||
| refreshSyncInformation(); | |||
| } | |||
| @Override | |||
| @@ -66,12 +70,12 @@ public class HomeActivity extends AppCompatActivity { | |||
| @Override | |||
| public boolean onOptionsItemSelected(MenuItem item) { | |||
| if (item.getItemId() == R.id.menu_settings) { | |||
| startActivity(new Intent(HomeActivity.this, PreferenceActivity.class)); | |||
| return true; | |||
| } | |||
| if (item.getItemId() == R.id.menu_profile) { | |||
| Intent profile = new Intent(HomeActivity.this, ProfileActivity.class); | |||
| startActivity(profile); | |||
| startActivity(new Intent(HomeActivity.this, ProfileActivity.class)); | |||
| return true; | |||
| } | |||
| @@ -98,21 +102,59 @@ public class HomeActivity extends AppCompatActivity { | |||
| sync_fab.setOnClickListener(onFabClicked); | |||
| } | |||
| private void populateRecyclerView() { | |||
| private void refreshSyncInformation () { | |||
| List<UploadInformation> uploadInformationList = preferenceManager.getUploadInformation(); | |||
| TextView empty = findViewById(R.id.emtpy); | |||
| // no sync information available | |||
| if (uploadInformationList == null) { | |||
| empty.setVisibility(View.VISIBLE); | |||
| recyclerView.setVisibility(View.GONE); | |||
| return; | |||
| } | |||
| // sync information available | |||
| empty.setVisibility(View.GONE); | |||
| recyclerView.setVisibility(View.VISIBLE); | |||
| SyncAdapter syncAdapter = new SyncAdapter(uploadInformationList, HomeActivity.this); | |||
| SyncAdapter adapter = (SyncAdapter) recyclerView.getAdapter(); | |||
| assert adapter != null; | |||
| adapter.setUploadInformationList(uploadInformationList); | |||
| } | |||
| private void initializeRecyclerView() { | |||
| SyncAdapter syncAdapter = new SyncAdapter(HomeActivity.this); | |||
| syncAdapter.setOnDeleteClickListener(new IOnItemClickListener<UploadInformation>() { | |||
| @Override | |||
| public void onItemClick(final UploadInformation clickedObject) { | |||
| DialogManager.deleteSyncInformationDialog(HomeActivity.this, new DialogManager.IDialogFeedback() { | |||
| @Override | |||
| public void positive() { | |||
| boolean status = preferenceManager.removeUploadInformation(clickedObject.getId()); | |||
| if (status) { | |||
| Snackbar.make(layout, "Successfully deleted sync information", Snackbar.LENGTH_LONG).show(); | |||
| refreshSyncInformation(); | |||
| } else { | |||
| Snackbar.make(layout, "Failed to delete sync information", Snackbar.LENGTH_LONG).show(); | |||
| } | |||
| } | |||
| @Override | |||
| public void negative() { } | |||
| }); | |||
| } | |||
| }); | |||
| syncAdapter.setOnRetryClickListener(new IOnItemClickListener<UploadInformation>() { | |||
| @Override | |||
| public void onItemClick(UploadInformation clickedObject) { | |||
| Intent upload = new Intent(HomeActivity.this, UploadActivity.class); | |||
| upload.putExtra(UploadActivity.EXTRA_UPLOAD_INFO, new Gson().toJson(clickedObject)); | |||
| startActivity(upload); | |||
| } | |||
| }); | |||
| recyclerView.setAdapter(syncAdapter); | |||
| } | |||
| } | |||
| @@ -1,17 +1,23 @@ | |||
| package lu.circl.mispbump.activities; | |||
| import android.content.Intent; | |||
| import android.net.Uri; | |||
| import android.os.Bundle; | |||
| import android.support.constraint.ConstraintLayout; | |||
| import android.support.design.widget.Snackbar; | |||
| import android.support.design.widget.TextInputLayout; | |||
| import android.support.v7.app.ActionBar; | |||
| import android.support.v7.app.AppCompatActivity; | |||
| import android.support.v7.widget.Toolbar; | |||
| import androidx.constraintlayout.widget.ConstraintLayout; | |||
| import com.google.android.material.snackbar.Snackbar; | |||
| import com.google.android.material.textfield.TextInputLayout; | |||
| import androidx.appcompat.app.ActionBar; | |||
| import androidx.appcompat.app.AppCompatActivity; | |||
| import androidx.appcompat.widget.Toolbar; | |||
| import android.text.Editable; | |||
| import android.text.TextUtils; | |||
| import android.text.TextWatcher; | |||
| import android.view.Menu; | |||
| import android.view.MenuItem; | |||
| import android.view.View; | |||
| import android.webkit.WebSettings; | |||
| import android.webkit.WebView; | |||
| import android.widget.Button; | |||
| import android.widget.ProgressBar; | |||
| @@ -40,7 +46,27 @@ public class LoginActivity extends AppCompatActivity { | |||
| protected void onCreate(Bundle savedInstanceState) { | |||
| super.onCreate(savedInstanceState); | |||
| setContentView(R.layout.activity_login); | |||
| initializeViews(); | |||
| } | |||
| @Override | |||
| public boolean onCreateOptionsMenu(Menu menu) { | |||
| getMenuInflater().inflate(R.menu.menu_login, menu); | |||
| return true; | |||
| } | |||
| @Override | |||
| public boolean onOptionsItemSelected(MenuItem item) { | |||
| if (item.getItemId() == R.id.menu_login_help) { | |||
| DialogManager.loginHelpDialog(LoginActivity.this); | |||
| return true; | |||
| } | |||
| // invoke superclass to handle unrecognized item (eg. homeAsUp) | |||
| return super.onOptionsItemSelected(item); | |||
| } | |||
| private void initializeViews() { | |||
| // populate Toolbar (Actionbar) | |||
| Toolbar myToolbar = findViewById(R.id.appbar); | |||
| setSupportActionBar(myToolbar); | |||
| @@ -51,34 +77,15 @@ public class LoginActivity extends AppCompatActivity { | |||
| } | |||
| constraintLayout = findViewById(R.id.rootLayout); | |||
| progressBar = findViewById(R.id.login_progressbar); | |||
| serverUrl = findViewById(R.id.login_server_url); | |||
| serverAutomationKey = findViewById(R.id.login_automation_key); | |||
| Button downloadInfoButton = findViewById(R.id.login_download_button); | |||
| downloadInfoButton.setOnClickListener(onClickDownload); | |||
| preferenceManager = PreferenceManager.getInstance(this); | |||
| } | |||
| @Override | |||
| public boolean onCreateOptionsMenu(Menu menu) { | |||
| getMenuInflater().inflate(R.menu.menu_login, menu); | |||
| return true; | |||
| } | |||
| @Override | |||
| public boolean onOptionsItemSelected(MenuItem item) { | |||
| switch (item.getItemId()) { | |||
| case R.id.menu_login_help: | |||
| DialogManager.loginHelpDialog(LoginActivity.this); | |||
| return true; | |||
| default: | |||
| // invoke superclass to handle unrecognized item (eg. homeAsUp) | |||
| return super.onOptionsItemSelected(item); | |||
| progressBar = findViewById(R.id.login_progressbar); | |||
| } | |||
| preferenceManager = PreferenceManager.getInstance(this); | |||
| } | |||
| /** | |||
| @@ -112,6 +119,7 @@ public class LoginActivity extends AppCompatActivity { | |||
| // save authkey | |||
| preferenceManager.setAutomationKey(authkey); | |||
| // save url | |||
| preferenceManager.setServerUrl(url); | |||
| @@ -122,20 +130,29 @@ public class LoginActivity extends AppCompatActivity { | |||
| progressBar.setVisibility(View.VISIBLE); | |||
| // get my user information and the organisation associated with my user | |||
| mispRestClient.getMyUser(new MispRestClient.UserCallback() { | |||
| mispRestClient.isAvailable(new MispRestClient.AvailableCallback() { | |||
| @Override | |||
| public void success(final User user) { | |||
| preferenceManager.setUserInfo(user); | |||
| mispRestClient.getOrganisation(user.org_id, new MispRestClient.OrganisationCallback() { | |||
| public void available() { | |||
| mispRestClient.getMyUser(new MispRestClient.UserCallback() { | |||
| @Override | |||
| public void success(Organisation organisation) { | |||
| preferenceManager.setUserOrgInfo(organisation); | |||
| progressBar.setVisibility(View.GONE); | |||
| Intent home = new Intent(getApplicationContext(), HomeActivity.class); | |||
| startActivity(home); | |||
| finish(); | |||
| public void success(final User user) { | |||
| preferenceManager.setUserInfo(user); | |||
| mispRestClient.getOrganisation(user.org_id, new MispRestClient.OrganisationCallback() { | |||
| @Override | |||
| public void success(Organisation organisation) { | |||
| preferenceManager.setUserOrgInfo(organisation); | |||
| progressBar.setVisibility(View.GONE); | |||
| Intent home = new Intent(getApplicationContext(), HomeActivity.class); | |||
| startActivity(home); | |||
| finish(); | |||
| } | |||
| @Override | |||
| public void failure(String error) { | |||
| progressBar.setVisibility(View.GONE); | |||
| Snackbar.make(constraintLayout, error, Snackbar.LENGTH_LONG).show(); | |||
| } | |||
| }); | |||
| } | |||
| @Override | |||
| @@ -147,9 +164,10 @@ public class LoginActivity extends AppCompatActivity { | |||
| } | |||
| @Override | |||
| public void failure(String error) { | |||
| public void unavailable(String error) { | |||
| progressBar.setVisibility(View.GONE); | |||
| Snackbar.make(constraintLayout, error, Snackbar.LENGTH_LONG).show(); | |||
| Snackbar sb = Snackbar.make(constraintLayout, error, Snackbar.LENGTH_LONG); | |||
| sb.show(); | |||
| } | |||
| }); | |||
| } | |||
| @@ -162,7 +180,13 @@ public class LoginActivity extends AppCompatActivity { | |||
| * @return true or false | |||
| */ | |||
| private boolean isValidUrl(String url) { | |||
| return url.startsWith("https://") || url.startsWith("http://"); | |||
| Uri uri = Uri.parse(url); | |||
| if (uri == null) { | |||
| return false; | |||
| } | |||
| return uri.getScheme() != null; | |||
| } | |||
| /** | |||
| @@ -0,0 +1,35 @@ | |||
| package lu.circl.mispbump.activities; | |||
| import androidx.appcompat.app.AppCompatActivity; | |||
| import android.os.Bundle; | |||
| import android.view.View; | |||
| import android.widget.Button; | |||
| import lu.circl.mispbump.R; | |||
| import lu.circl.mispbump.auxiliary.PreferenceManager; | |||
| public class PreferenceActivity extends AppCompatActivity { | |||
| private PreferenceManager preferenceManager; | |||
| @Override | |||
| protected void onCreate(Bundle savedInstanceState) { | |||
| super.onCreate(savedInstanceState); | |||
| setContentView(R.layout.activity_preference); | |||
| preferenceManager = PreferenceManager.getInstance(PreferenceActivity.this); | |||
| initializeViews(); | |||
| } | |||
| private void initializeViews() { | |||
| Button deleteSyncs = findViewById(R.id.deleteSyncs); | |||
| deleteSyncs.setOnClickListener(new View.OnClickListener() { | |||
| @Override | |||
| public void onClick(View v) { | |||
| preferenceManager.clearUploadInformation(); | |||
| } | |||
| }); | |||
| } | |||
| } | |||
| @@ -6,20 +6,17 @@ import android.graphics.Shader; | |||
| import android.graphics.drawable.AnimatedVectorDrawable; | |||
| import android.graphics.drawable.Drawable; | |||
| import android.os.Bundle; | |||
| import android.support.design.widget.CoordinatorLayout; | |||
| import android.support.design.widget.FloatingActionButton; | |||
| import android.support.design.widget.Snackbar; | |||
| import android.support.v4.widget.ContentLoadingProgressBar; | |||
| import android.support.v7.app.ActionBar; | |||
| import android.support.v7.app.AlertDialog; | |||
| import android.support.v7.app.AppCompatActivity; | |||
| import android.support.v7.widget.Toolbar; | |||
| import androidx.coordinatorlayout.widget.CoordinatorLayout; | |||
| import com.google.android.material.floatingactionbutton.FloatingActionButton; | |||
| import com.google.android.material.snackbar.Snackbar; | |||
| import androidx.appcompat.app.ActionBar; | |||
| import androidx.appcompat.app.AlertDialog; | |||
| import androidx.appcompat.app.AppCompatActivity; | |||
| import androidx.appcompat.widget.Toolbar; | |||
| import android.view.Menu; | |||
| import android.view.MenuItem; | |||
| import android.view.View; | |||
| import android.view.animation.RotateAnimation; | |||
| import android.widget.ImageView; | |||
| import android.widget.ProgressBar; | |||
| import android.widget.TextView; | |||
| import java.util.Random; | |||
| @@ -85,19 +82,15 @@ public class ProfileActivity extends AppCompatActivity { | |||
| MaterialPreferenceText uuid = findViewById(R.id.uuid); | |||
| uuid.setSubText(organisation.uuid); | |||
| uuid.setOnClickListener(new View.OnClickListener() { | |||
| @Override | |||
| public void onClick(View v) { | |||
| TextView tv = v.findViewById(R.id.material_preference_subtitle); | |||
| Snackbar.make(rootLayout, "clicked: " + tv.getText().toString(), Snackbar.LENGTH_LONG).show(); | |||
| } | |||
| }); | |||
| MaterialPreferenceText nationality = findViewById(R.id.nationality); | |||
| nationality.setSubText(organisation.nationality); | |||
| MaterialPreferenceText sector = findViewById(R.id.sector); | |||
| sector.setSubText(organisation.sector); | |||
| if (organisation.sector == null) { | |||
| sector.setVisibility(View.GONE); | |||
| } else { | |||
| sector.setSubText(organisation.sector); | |||
| } | |||
| MaterialPreferenceText description = findViewById(R.id.description); | |||
| description.setSubText(organisation.description); | |||
| @@ -2,7 +2,7 @@ package lu.circl.mispbump.activities; | |||
| import android.content.Intent; | |||
| import android.os.Bundle; | |||
| import android.support.v7.app.AppCompatActivity; | |||
| import androidx.appcompat.app.AppCompatActivity; | |||
| import lu.circl.mispbump.auxiliary.PreferenceManager; | |||
| import lu.circl.mispbump.restful_client.User; | |||
| @@ -1,41 +1,38 @@ | |||
| package lu.circl.mispbump.activities; | |||
| import android.animation.Animator; | |||
| import android.animation.AnimatorListenerAdapter; | |||
| import android.content.Intent; | |||
| import android.graphics.Bitmap; | |||
| import android.os.Bundle; | |||
| import android.support.design.bottomappbar.BottomAppBar; | |||
| import android.support.design.widget.CoordinatorLayout; | |||
| import android.support.design.widget.FloatingActionButton; | |||
| import android.support.design.widget.Snackbar; | |||
| import android.support.v4.app.FragmentManager; | |||
| import android.support.v4.app.FragmentTransaction; | |||
| import android.support.v7.app.ActionBar; | |||
| import android.support.v7.app.AppCompatActivity; | |||
| import android.support.v7.widget.Toolbar; | |||
| import android.util.Log; | |||
| import android.view.View; | |||
| import android.widget.ImageButton; | |||
| import android.widget.ImageView; | |||
| import android.widget.TextView; | |||
| import androidx.appcompat.app.AppCompatActivity; | |||
| import androidx.coordinatorlayout.widget.CoordinatorLayout; | |||
| import androidx.fragment.app.FragmentManager; | |||
| import androidx.fragment.app.FragmentTransaction; | |||
| import com.google.android.material.bottomsheet.BottomSheetBehavior; | |||
| import com.google.android.material.snackbar.Snackbar; | |||
| import com.google.gson.Gson; | |||
| import com.google.gson.JsonSyntaxException; | |||
| import java.security.NoSuchAlgorithmException; | |||
| import java.security.PublicKey; | |||
| import java.security.spec.InvalidKeySpecException; | |||
| import java.util.List; | |||
| import lu.circl.mispbump.R; | |||
| import lu.circl.mispbump.auxiliary.DialogManager; | |||
| import lu.circl.mispbump.auxiliary.PreferenceManager; | |||
| import lu.circl.mispbump.auxiliary.QrCodeGenerator; | |||
| import lu.circl.mispbump.auxiliary.RandomString; | |||
| import lu.circl.mispbump.cam.CameraFragment; | |||
| import lu.circl.mispbump.custom_views.ExtendedBottomSheetBehavior; | |||
| import lu.circl.mispbump.fragments.SyncOptionsFragment; | |||
| import lu.circl.mispbump.models.SyncInformation; | |||
| import lu.circl.mispbump.models.UploadInformation; | |||
| import lu.circl.mispbump.restful_client.MispRestClient; | |||
| import lu.circl.mispbump.restful_client.MispServer; | |||
| import lu.circl.mispbump.restful_client.Organisation; | |||
| import lu.circl.mispbump.restful_client.Server; | |||
| import lu.circl.mispbump.restful_client.User; | |||
| import lu.circl.mispbump.security.DiffieHellman; | |||
| /** | |||
| @@ -45,365 +42,382 @@ import lu.circl.mispbump.security.DiffieHellman; | |||
| */ | |||
| public class SyncActivity extends AppCompatActivity { | |||
| private static final String TAG = "SyncActivity"; | |||
| // layout | |||
| private CoordinatorLayout layout; | |||
| private ImageView qrCodeView; | |||
| private FloatingActionButton continueButton; | |||
| private ImageView qrCodeView, bottomSheetIcon; | |||
| private TextView bottomSheetText; | |||
| private ImageButton prevButton, nextButton; | |||
| private ExtendedBottomSheetBehavior bottomSheetBehavior; | |||
| private CameraFragment cameraFragment; | |||
| // dependencies | |||
| private PreferenceManager preferenceManager; | |||
| private DiffieHellman diffieHellman; | |||
| private MispRestClient restClient; | |||
| private UploadInformation uploadInformation; | |||
| private SyncState currentSyncState = SyncState.publicKeyExchange; | |||
| // fragments | |||
| private CameraFragment cameraFragment; | |||
| private SyncOptionsFragment syncOptionsFragment; | |||
| private PreferenceManager preferenceManager; | |||
| // qr codes | |||
| private QrCodeGenerator qrCodeGenerator; | |||
| private Bitmap publicKeyQr, syncInfoQr; | |||
| private SyncState currentSyncState = SyncState.settings; | |||
| private enum SyncState { | |||
| publicKeyExchange, | |||
| dataExchange | |||
| settings(0), | |||
| publicKeyExchange(1), | |||
| dataExchange(2); | |||
| private final int value; | |||
| SyncState(final int value) { | |||
| this.value = value; | |||
| } | |||
| } | |||
| @Override | |||
| protected void onCreate(Bundle savedInstanceState) { | |||
| super.onCreate(savedInstanceState); | |||
| setContentView(R.layout.activity_sync_2); | |||
| // BottomAppBar myToolbar = findViewById(R.id.bottomNavigation); | |||
| // setSupportActionBar(myToolbar); | |||
| // | |||
| // ActionBar ab = getSupportActionBar(); | |||
| // if (ab != null) { | |||
| // ab.setDisplayHomeAsUpEnabled(true); | |||
| // } | |||
| setContentView(R.layout.activity_sync); | |||
| initializeViews(); | |||
| } | |||
| private void initializeViews() { | |||
| // Root Layout | |||
| layout = findViewById(R.id.rootLayout); | |||
| // prev button | |||
| prevButton = findViewById(R.id.prevButton); | |||
| prevButton.setOnClickListener(onPrevClicked); | |||
| // next button | |||
| nextButton = findViewById(R.id.nextButton); | |||
| nextButton.setOnClickListener(onNextClicked); | |||
| // QR Code View | |||
| qrCodeView = findViewById(R.id.qrcode); | |||
| // continueButton = findViewById(R.id.fab); | |||
| // continueButton.setOnClickListener(onContinueClicked); | |||
| // continueButton.hide(); | |||
| qrCodeGenerator = new QrCodeGenerator(SyncActivity.this); | |||
| diffieHellman = DiffieHellman.getInstance(); | |||
| restClient = new MispRestClient(this); | |||
| bottomSheetIcon = findViewById(R.id.bottomSheetIcon); | |||
| bottomSheetText = findViewById(R.id.bottomSheetText); | |||
| diffieHellman = DiffieHellman.getInstance(); | |||
| preferenceManager = PreferenceManager.getInstance(this); | |||
| enableSyncOptionsFragment(); | |||
| View bottomSheet = findViewById(R.id.bottomSheet); | |||
| bottomSheetBehavior = (ExtendedBottomSheetBehavior) BottomSheetBehavior.from(bottomSheet); | |||
| bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); | |||
| bottomSheetBehavior.setSwipeable(false); | |||
| bottomSheetBehavior.setHideable(false); | |||
| publicKeyQr = generatePublicKeyQr(); | |||
| switchState(SyncState.settings); | |||
| } | |||
| /** | |||
| * This callback is called at the end of each sync step. | |||
| * Called when "next button" is pressed | |||
| */ | |||
| private View.OnClickListener onContinueClicked = new View.OnClickListener() { | |||
| private View.OnClickListener onNextClicked = new View.OnClickListener() { | |||
| @Override | |||
| public void onClick(View v) { | |||
| switch (currentSyncState) { | |||
| case settings: | |||
| uploadInformation.setCached(syncOptionsFragment.cache.isChecked()); | |||
| uploadInformation.setPush(syncOptionsFragment.push.isChecked()); | |||
| uploadInformation.setPull(syncOptionsFragment.pull.isChecked()); | |||
| uploadInformation.setAllowSelfSigned(syncOptionsFragment.allowSelfSigned.isChecked()); | |||
| cameraFragment.setReadQrEnabled(false); | |||
| switchState(SyncState.publicKeyExchange); | |||
| break; | |||
| case publicKeyExchange: | |||
| switchState(SyncState.dataExchange); | |||
| break; | |||
| case dataExchange: | |||
| Intent upload = new Intent(SyncActivity.this, UploadActivity.class); | |||
| upload.putExtra(UploadActivity.EXTRA_UPLOAD_INFO, new Gson().toJson(uploadInformation)); | |||
| startActivity(upload); | |||
| overridePendingTransition(R.anim.slide_in_right, android.R.anim.slide_out_right); | |||
| finish(); | |||
| break; | |||
| } | |||
| } | |||
| }; | |||
| /** | |||
| * Called when "prev button" is clicked | |||
| */ | |||
| private View.OnClickListener onPrevClicked = new View.OnClickListener() { | |||
| @Override | |||
| public void onClick(View v) { | |||
| switch (currentSyncState) { | |||
| case settings: | |||
| finish(); | |||
| break; | |||
| case publicKeyExchange: | |||
| DialogManager.confirmProceedDialog(SyncActivity.this, | |||
| new DialogManager.IDialogFeedback() { | |||
| @Override | |||
| public void positive() { | |||
| currentSyncState = SyncState.dataExchange; | |||
| continueButton.hide(); | |||
| cameraFragment.setReadQrEnabled(true); | |||
| showInformationQr(); | |||
| } | |||
| @Override | |||
| public void negative() { | |||
| } | |||
| }); | |||
| switchState(SyncState.settings); | |||
| break; | |||
| case dataExchange: | |||
| DialogManager.confirmProceedDialog(SyncActivity.this, new DialogManager.IDialogFeedback() { | |||
| @Override | |||
| public void positive() { | |||
| startUpload(); | |||
| } | |||
| @Override | |||
| public void negative() { | |||
| } | |||
| }); | |||
| switchState(SyncState.publicKeyExchange); | |||
| break; | |||
| } | |||
| } | |||
| }; | |||
| /** | |||
| * Callback for the camera fragment. | |||
| * Delivers the content of a scanned QR code. | |||
| * Called when the camera fragment detects a qr code | |||
| */ | |||
| private CameraFragment.QrScanCallback onQrCodeScanned = new CameraFragment.QrScanCallback() { | |||
| @Override | |||
| public void qrScanResult(String qrData) { | |||
| cameraFragment.setReadQrEnabled(false); | |||
| switch (currentSyncState) { | |||
| case publicKeyExchange: | |||
| try { | |||
| final PublicKey pk = DiffieHellman.publicKeyFromString(qrData); | |||
| diffieHellman.setForeignPublicKey(pk); | |||
| syncInfoQr = generateSyncInfoQr(); | |||
| runOnUiThread(new Runnable() { | |||
| @Override | |||
| public void run() { | |||
| continueButton.show(); | |||
| Snackbar sb = Snackbar.make(continueButton, "Public key received", Snackbar.LENGTH_LONG); | |||
| sb.setAction("Details", new View.OnClickListener() { | |||
| @Override | |||
| public void onClick(View v) { | |||
| DialogManager.publicKeyDialog(pk, SyncActivity.this, null); | |||
| } | |||
| }); | |||
| sb.show(); | |||
| nextButton.setVisibility(View.VISIBLE); | |||
| cameraFragment.disablePreview(); | |||
| qrReceivedFeedback(); | |||
| } | |||
| }); | |||
| } catch (InvalidKeySpecException | NoSuchAlgorithmException e) { | |||
| Snackbar.make(layout, "Invalid key", Snackbar.LENGTH_SHORT).show(); | |||
| cameraFragment.setReadQrEnabled(true); | |||
| } | |||
| break; | |||
| case dataExchange: | |||
| cameraFragment.setReadQrEnabled(false); | |||
| final SyncInformation remoteSyncInfo = new Gson().fromJson(diffieHellman.decrypt(qrData), SyncInformation.class); | |||
| DialogManager.syncInformationDialog(remoteSyncInfo, | |||
| SyncActivity.this, | |||
| new DialogManager.IDialogFeedback() { | |||
| @Override | |||
| public void positive() { | |||
| uploadInformation.setRemote(remoteSyncInfo); | |||
| continueButton.show(); | |||
| } | |||
| try { | |||
| final SyncInformation remoteSyncInfo = new Gson().fromJson(diffieHellman.decrypt(qrData), SyncInformation.class); | |||
| uploadInformation.setRemote(remoteSyncInfo); | |||
| @Override | |||
| public void negative() { | |||
| cameraFragment.setReadQrEnabled(true); | |||
| } | |||
| }); | |||
| runOnUiThread(new Runnable() { | |||
| @Override | |||
| public void run() { | |||
| cameraFragment.disablePreview(); | |||
| nextButton.setVisibility(View.VISIBLE); | |||
| qrReceivedFeedback(); | |||
| } | |||
| }); | |||
| } catch (JsonSyntaxException e) { | |||
| Snackbar.make(layout, "Sync information unreadable", Snackbar.LENGTH_SHORT).show(); | |||
| cameraFragment.setReadQrEnabled(true); | |||
| } | |||
| break; | |||
| } | |||
| } | |||
| }; | |||
| private void startUpload() { | |||
| // check if misp instance is available | |||
| restClient.isAvailable(new MispRestClient.AvailableCallback() { | |||
| @Override | |||
| public void unavailable() { | |||
| Snackbar sb = Snackbar.make(layout, "MISP instance not available", Snackbar.LENGTH_LONG); | |||
| sb.setAction("Retry", new View.OnClickListener() { | |||
| @Override | |||
| public void onClick(View v) { | |||
| startUpload(); // TODO check if this works | |||
| } | |||
| }); | |||
| sb.show(); | |||
| } | |||
| @Override | |||
| public void available() { | |||
| restClient.addOrganisation(uploadInformation.getRemote().organisation, new MispRestClient.OrganisationCallback() { | |||
| @Override | |||
| public void success(final Organisation organisation) { | |||
| // create syncUser object from syncInfo | |||
| User syncUser = new User(); | |||
| syncUser.org_id = organisation.id; | |||
| syncUser.role_id = User.ROLE_SYNC_USER; | |||
| // syncuser_ORG@REMOTE_ORG_EMAIL_DOMAIN | |||
| String emailSaveOrgName = organisation.name.replace(" ", "").toLowerCase(); | |||
| syncUser.email = "syncuser_" + emailSaveOrgName + "@misp.de"; | |||
| syncUser.password = uploadInformation.getRemote().syncUserPassword; | |||
| syncUser.authkey = uploadInformation.getRemote().syncUserAuthkey; | |||
| syncUser.termsaccepted = true; | |||
| // add user to local organisation | |||
| restClient.addUser(syncUser, new MispRestClient.UserCallback() { | |||
| @Override | |||
| public void success(User user) { | |||
| Server server = new Server(); | |||
| server.name = organisation.name + "'s Sync Server"; | |||
| server.url = uploadInformation.getRemote().baseUrl; | |||
| server.remote_org_id = organisation.id; | |||
| server.authkey = uploadInformation.getLocal().syncUserAuthkey; | |||
| server.self_signed = true; | |||
| restClient.addServer(server, new MispRestClient.ServerCallback() { | |||
| @Override | |||
| public void success(List<MispServer> servers) { | |||
| } | |||
| @Override | |||
| public void success(MispServer server) { | |||
| } | |||
| @Override | |||
| public void success(Server server) { | |||
| uploadInformation.setCurrentSyncStatus(UploadInformation.SyncStatus.COMPLETE); | |||
| preferenceManager.setUploadInformation(uploadInformation); | |||
| finish(); | |||
| } | |||
| @Override | |||
| public void failure(String error) { | |||
| uploadInformation.setCurrentSyncStatus(UploadInformation.SyncStatus.FAILURE); | |||
| preferenceManager.setUploadInformation(uploadInformation); | |||
| Snackbar.make(layout, error, Snackbar.LENGTH_LONG).show(); | |||
| Log.e(TAG, error); | |||
| } | |||
| }); | |||
| } | |||
| @Override | |||
| public void failure(String error) { | |||
| uploadInformation.setCurrentSyncStatus(UploadInformation.SyncStatus.FAILURE); | |||
| preferenceManager.setUploadInformation(uploadInformation); | |||
| Snackbar.make(layout, error, Snackbar.LENGTH_LONG).show(); | |||
| Log.e(TAG, error); | |||
| } | |||
| }); | |||
| } | |||
| @Override | |||
| public void failure(String error) { | |||
| uploadInformation.setCurrentSyncStatus(UploadInformation.SyncStatus.FAILURE); | |||
| preferenceManager.setUploadInformation(uploadInformation); | |||
| Snackbar.make(layout, error, Snackbar.LENGTH_LONG).show(); | |||
| Log.e(TAG, error); | |||
| } | |||
| }); | |||
| } | |||
| }); | |||
| private void switchUiState(SyncState state) { | |||
| bottomSheetIcon.setVisibility(View.INVISIBLE); | |||
| bottomSheetBehavior.setSwipeable(false); | |||
| bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); | |||
| switch (state) { | |||
| case settings: | |||
| prevButton.setImageDrawable(getDrawable(R.drawable.ic_close)); | |||
| prevButton.setVisibility(View.VISIBLE); | |||
| nextButton.setVisibility(View.VISIBLE); | |||
| hideQrCode(); | |||
| break; | |||
| case publicKeyExchange: | |||
| prevButton.setImageDrawable(getDrawable(R.drawable.ic_arrow_back)); | |||
| prevButton.setVisibility(View.VISIBLE); | |||
| nextButton.setImageDrawable(getDrawable(R.drawable.ic_arrow_forward)); | |||
| nextButton.setVisibility(View.GONE); | |||
| showQrCode(publicKeyQr); | |||
| break; | |||
| case dataExchange: | |||
| prevButton.setImageDrawable(getDrawable(R.drawable.ic_arrow_back)); | |||
| prevButton.setVisibility(View.VISIBLE); | |||
| nextButton.setImageDrawable(getDrawable(R.drawable.ic_cloud_upload)); | |||
| nextButton.setVisibility(View.GONE); | |||
| cameraFragment.enablePreview(); | |||
| cameraFragment.setReadQrEnabled(true); | |||
| showQrCode(syncInfoQr); | |||
| break; | |||
| } | |||
| } | |||
| /** | |||
| * Creates the camera fragment used to scan the QR codes. | |||
| * Automatically starts processing images (search QR codes). | |||
| */ | |||
| private void enableCameraFragment() { | |||
| cameraFragment = new CameraFragment(); | |||
| private void switchState(SyncState state) { | |||
| FragmentManager fragmentManager = getSupportFragmentManager(); | |||
| FragmentTransaction transaction = fragmentManager.beginTransaction(); | |||
| transaction.replace(R.id.sync_fragment_container, cameraFragment, cameraFragment.getClass().getSimpleName()); | |||
| transaction.commit(); | |||
| cameraFragment.setReadQrEnabled(true); | |||
| cameraFragment.setOnQrAvailableListener(onQrCodeScanned); | |||
| } | |||
| if (currentSyncState != state) { | |||
| if (state.value < currentSyncState.value) { | |||
| transaction.setCustomAnimations(android.R.anim.slide_in_left, android.R.anim.slide_out_right); | |||
| } else { | |||
| transaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left); | |||
| } | |||
| } | |||
| /** | |||
| * Creates fragment to tweak sync options. | |||
| */ | |||
| private void enableSyncOptionsFragment() { | |||
| SyncOptionsFragment syncOptionsFragment = new SyncOptionsFragment(); | |||
| currentSyncState = state; | |||
| syncOptionsFragment.setOnOptionsReadyCallback(new SyncOptionsFragment.OptionsReadyCallback() { | |||
| @Override | |||
| public void ready(boolean share_events, boolean push, boolean pull, boolean caching) { | |||
| showPublicKeyQr(); | |||
| enableCameraFragment(); | |||
| } | |||
| }); | |||
| switchUiState(currentSyncState); | |||
| FragmentManager fragmentManager = getSupportFragmentManager(); | |||
| FragmentTransaction transaction = fragmentManager.beginTransaction(); | |||
| transaction.replace(R.id.sync_fragment_container, syncOptionsFragment, syncOptionsFragment.getClass().getSimpleName()); | |||
| transaction.commit(); | |||
| } | |||
| switch (currentSyncState) { | |||
| case settings: | |||
| String fragTag = SyncOptionsFragment.class.getSimpleName(); | |||
| /** | |||
| * Display QR code that contains the public key . | |||
| */ | |||
| private void showPublicKeyQr() { | |||
| QrCodeGenerator qrCodeGenerator = new QrCodeGenerator(this); | |||
| Bitmap bm = qrCodeGenerator.generateQrCode(DiffieHellman.publicKeyToString(diffieHellman.getPublicKey())); | |||
| qrCodeView.setImageBitmap(bm); | |||
| qrCodeView.setVisibility(View.VISIBLE); | |||
| syncOptionsFragment = (SyncOptionsFragment) fragmentManager.findFragmentByTag(fragTag); | |||
| if (syncOptionsFragment == null) { | |||
| syncOptionsFragment = new SyncOptionsFragment(); | |||
| } | |||
| transaction.replace(R.id.sync_fragment_container, syncOptionsFragment, fragTag); | |||
| transaction.commit(); | |||
| break; | |||
| case publicKeyExchange: | |||
| fragTag = CameraFragment.class.getSimpleName(); | |||
| cameraFragment = (CameraFragment) fragmentManager.findFragmentByTag(fragTag); | |||
| if (cameraFragment == null) { | |||
| cameraFragment = new CameraFragment(); | |||
| cameraFragment.setOnQrAvailableListener(onQrCodeScanned); | |||
| } | |||
| transaction.replace(R.id.sync_fragment_container, cameraFragment, fragTag); | |||
| transaction.commit(); | |||
| break; | |||
| case dataExchange: | |||
| fragTag = CameraFragment.class.getSimpleName(); | |||
| cameraFragment = (CameraFragment) fragmentManager.findFragmentByTag(fragTag); | |||
| if (cameraFragment == null) { | |||
| cameraFragment = new CameraFragment(); | |||
| cameraFragment.setOnQrAvailableListener(onQrCodeScanned); | |||
| } | |||
| transaction.replace(R.id.sync_fragment_container, cameraFragment, fragTag); | |||
| transaction.commit(); | |||
| break; | |||
| } | |||
| } | |||
| /** | |||
| * Display QR code that contains mandatory information for a sync. | |||
| */ | |||
| private void showInformationQr() { | |||
| PreferenceManager preferenceManager = PreferenceManager.getInstance(this); | |||
| SyncInformation syncInformation = new SyncInformation(); | |||
| private Bitmap generatePublicKeyQr() { | |||
| return qrCodeGenerator.generateQrCode(DiffieHellman.publicKeyToString(diffieHellman.getPublicKey())); | |||
| } | |||
| syncInformation.organisation = preferenceManager.getUserOrganisation().syncOrganisation(); | |||
| private Bitmap generateSyncInfoQr() { | |||
| SyncInformation syncInformation = new SyncInformation(); | |||
| syncInformation.organisation = preferenceManager.getUserOrganisation().toSyncOrganisation(); | |||
| syncInformation.syncUserAuthkey = new RandomString(40).nextString(); | |||
| syncInformation.baseUrl = preferenceManager.getServerUrl(); | |||
| syncInformation.syncUserPassword = "abcdefghijklmnop"; | |||
| syncInformation.syncUserPassword = new RandomString(16).nextString(); | |||
| String myEmailDomain = preferenceManager.getUserInfo().email.split("@")[1]; | |||
| syncInformation.syncUserEmail = "syncuser_[ORG]@" + myEmailDomain; | |||
| uploadInformation = new UploadInformation(syncInformation); | |||
| // encrypt serialized content | |||
| String encrypted = diffieHellman.encrypt(new Gson().toJson(syncInformation)); | |||
| // Generate QR code | |||
| QrCodeGenerator qrCodeGenerator = new QrCodeGenerator(this); | |||
| final Bitmap bm = qrCodeGenerator.generateQrCode(encrypted); | |||
| // generate QR code | |||
| return qrCodeGenerator.generateQrCode(encrypted); | |||
| } | |||
| private void showQrCode(final Bitmap bitmap) { | |||
| runOnUiThread(new Runnable() { | |||
| @Override | |||
| public void run() { | |||
| qrCodeView.setImageBitmap(bm); | |||
| qrCodeView.setImageBitmap(bitmap); | |||
| qrCodeView.setAlpha(0f); | |||
| qrCodeView.setVisibility(View.VISIBLE); | |||
| qrCodeView.setScaleX(0.9f); | |||
| qrCodeView.setScaleY(0.6f); | |||
| qrCodeView.animate() | |||
| .scaleX(1f) | |||
| .scaleY(1f) | |||
| .alpha(1f) | |||
| .setDuration(250) | |||
| .setListener(new AnimatorListenerAdapter() { | |||
| @Override | |||
| public void onAnimationEnd(Animator animation) { | |||
| qrCodeView.setVisibility(View.VISIBLE); | |||
| } | |||
| }); | |||
| } | |||
| }); | |||
| } | |||
| // /** | |||
| // * Display toast on UI thread. | |||
| // * | |||
| // * @param message message to display | |||
| // */ | |||
| // private void MakeToast(final String message) { | |||
| // this.runOnUiThread(new Runnable() { | |||
| // @Override | |||
| // public void run() { | |||
| // Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show(); | |||
| // } | |||
| // }); | |||
| // } | |||
| // private View.OnClickListener onGetServers = new View.OnClickListener() { | |||
| // @Override | |||
| // public void onClick(View v) { | |||
| // restClient.getServers(new MispRestClient.ServerCallback() { | |||
| // @Override | |||
| // public void success(List<MispServer> servers) { | |||
| // for (MispServer server : servers) { | |||
| // resultView.append(server.server.toString() + "\n\n"); | |||
| // resultView.append(server.organisation.toString() + "\n\n"); | |||
| // resultView.append(server.remoteOrg.toString()); | |||
| // } | |||
| // } | |||
| // | |||
| // @Override | |||
| // public void success(MispServer server) { | |||
| // | |||
| // } | |||
| // | |||
| // @Override | |||
| // public void failure(String error) { | |||
| // resultView.setText(error); | |||
| // } | |||
| // }); | |||
| // } | |||
| // }; | |||
| private void hideQrCode() { | |||
| if (qrCodeView.getVisibility() == View.GONE) { | |||
| return; | |||
| } | |||
| runOnUiThread(new Runnable() { | |||
| @Override | |||
| public void run() { | |||
| qrCodeView.setAlpha(1f); | |||
| qrCodeView.setVisibility(View.VISIBLE); | |||
| qrCodeView.setScaleX(1f); | |||
| qrCodeView.setScaleY(1f); | |||
| qrCodeView.animate() | |||
| .scaleX(0f) | |||
| .scaleY(0f) | |||
| .alpha(0f) | |||
| .setDuration(250) | |||
| .setListener(new AnimatorListenerAdapter() { | |||
| @Override | |||
| public void onAnimationEnd(Animator animation) { | |||
| qrCodeView.setVisibility(View.GONE); | |||
| } | |||
| }); | |||
| } | |||
| }); | |||
| } | |||
| private void qrReceivedFeedback() { | |||
| bottomSheetIcon.setScaleX(0f); | |||
| bottomSheetIcon.setScaleY(0f); | |||
| bottomSheetIcon.setVisibility(View.VISIBLE); | |||
| bottomSheetIcon.animate() | |||
| .scaleY(1f) | |||
| .scaleX(1f) | |||
| .setDuration(250); | |||
| bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); | |||
| bottomSheetBehavior.setSwipeable(true); | |||
| switch (currentSyncState) { | |||
| case publicKeyExchange: | |||
| bottomSheetText.setText("Received public key from partner"); | |||
| break; | |||
| case dataExchange: | |||
| bottomSheetText.setText("Received sync information from partner"); | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,274 @@ | |||
| package lu.circl.mispbump.activities; | |||
| import android.os.Bundle; | |||
| import android.view.MenuItem; | |||
| import android.view.View; | |||
| import androidx.appcompat.app.ActionBar; | |||
| import androidx.appcompat.app.AppCompatActivity; | |||
| import androidx.appcompat.widget.Toolbar; | |||
| import androidx.coordinatorlayout.widget.CoordinatorLayout; | |||
| import com.google.android.material.floatingactionbutton.FloatingActionButton; | |||
| import com.google.android.material.snackbar.Snackbar; | |||
| import com.google.gson.Gson; | |||
| import java.io.IOException; | |||
| import java.util.List; | |||
| import java.util.UUID; | |||
| import lu.circl.mispbump.R; | |||
| import lu.circl.mispbump.auxiliary.DialogManager; | |||
| import lu.circl.mispbump.auxiliary.PreferenceManager; | |||
| import lu.circl.mispbump.custom_views.UploadAction; | |||
| import lu.circl.mispbump.models.UploadInformation; | |||
| import lu.circl.mispbump.restful_client.MispRestClient; | |||
| import lu.circl.mispbump.restful_client.MispServer; | |||
| import lu.circl.mispbump.restful_client.Organisation; | |||
| import lu.circl.mispbump.restful_client.Server; | |||
| import lu.circl.mispbump.restful_client.User; | |||
| public class UploadActivity extends AppCompatActivity { | |||
| public static final String EXTRA_UPLOAD_INFO = "uploadInformation"; | |||
| private PreferenceManager preferenceManager; | |||
| private MispRestClient restClient; | |||
| private UploadInformation uploadInformation; | |||
| private CoordinatorLayout rootLayout; | |||
| private UploadAction availableAction, orgAction, userAction, serverAction; | |||
| @Override | |||
| protected void onCreate(Bundle savedInstanceState) { | |||
| super.onCreate(savedInstanceState); | |||
| setContentView(R.layout.activity_upload); | |||
| parseExtra(); | |||
| init(); | |||
| } | |||
| private void parseExtra() { | |||
| String uploadInfoString = getIntent().getStringExtra(EXTRA_UPLOAD_INFO); | |||
| uploadInformation = new Gson().fromJson(uploadInfoString, UploadInformation.class); | |||
| assert uploadInformation != null; | |||
| } | |||
| private void init() { | |||
| preferenceManager = PreferenceManager.getInstance(this); | |||
| restClient = new MispRestClient(this); | |||
| rootLayout = findViewById(R.id.rootLayout); | |||
| // toolbar | |||
| Toolbar toolbar = findViewById(R.id.toolbar); | |||
| setSupportActionBar(toolbar); | |||
| ActionBar ab = getSupportActionBar(); | |||
| assert ab != null; | |||
| ab.setDisplayShowTitleEnabled(true); | |||
| ab.setDisplayHomeAsUpEnabled(true); | |||
| ab.setHomeAsUpIndicator(R.drawable.ic_close); | |||
| // fab | |||
| FloatingActionButton fab = findViewById(R.id.fab); | |||
| fab.setOnClickListener(new View.OnClickListener() { | |||
| @Override | |||
| public void onClick(View v) { | |||
| startUpload(); | |||
| } | |||
| }); | |||
| availableAction = findViewById(R.id.availableAction); | |||
| orgAction = findViewById(R.id.orgAction); | |||
| userAction = findViewById(R.id.userAction); | |||
| serverAction = findViewById(R.id.serverAction); | |||
| } | |||
| @Override | |||
| public boolean onOptionsItemSelected(MenuItem item) { | |||
| switch (item.getItemId()) { | |||
| case android.R.id.home: | |||
| preferenceManager.addUploadInformation(uploadInformation); | |||
| finish(); | |||
| return true; | |||
| default: | |||
| return super.onOptionsItemSelected(item); | |||
| } | |||
| } | |||
| /** | |||
| * Start upload to misp instance. | |||
| */ | |||
| private void startUpload() { | |||
| availableAction.setCurrentUploadState(UploadAction.UploadState.LOADING); | |||
| restClient.isAvailable(availableCallback); | |||
| } | |||
| private User generateSyncUser(Organisation organisation) { | |||
| User syncUser = new User(); | |||
| syncUser.org_id = organisation.id; | |||
| syncUser.role_id = User.ROLE_SYNC_USER; | |||
| String emailSaveOrgName = organisation.name.replace(" ", "").toLowerCase(); | |||
| String syncUserEmailFormat = uploadInformation.getRemote().syncUserEmail; | |||
| syncUser.email = syncUserEmailFormat.replace("[ORG]", emailSaveOrgName); | |||
| uploadInformation.getRemote().syncUserEmail = syncUser.email; | |||
| syncUser.password = uploadInformation.getRemote().syncUserPassword; | |||
| syncUser.authkey = uploadInformation.getRemote().syncUserAuthkey; | |||
| syncUser.termsaccepted = true; | |||
| return syncUser; | |||
| } | |||
| private MispRestClient.AvailableCallback availableCallback = new MispRestClient.AvailableCallback() { | |||
| @Override | |||
| public void available() { | |||
| availableAction.setCurrentUploadState(UploadAction.UploadState.DONE); | |||
| orgAction.setCurrentUploadState(UploadAction.UploadState.LOADING); | |||
| Thread t = new Thread(new Runnable() { | |||
| @Override | |||
| public void run() { | |||
| try { | |||
| int orgId = organisationExists(); | |||
| if (orgId != -1) { | |||
| Snackbar.make(rootLayout, "exists", Snackbar.LENGTH_INDEFINITE).show(); | |||
| uploadInformation.getRemote().organisation.id = orgId; | |||
| // TODO if exists: add User | |||
| } else { | |||
| restClient.addOrganisation(uploadInformation.getRemote().organisation, organisationCallback); | |||
| Snackbar.make(rootLayout, "does not exist", Snackbar.LENGTH_INDEFINITE).show(); | |||
| } | |||
| } catch (IOException e) { | |||
| Snackbar.make(rootLayout, "Some error", Snackbar.LENGTH_INDEFINITE).show(); | |||
| e.printStackTrace(); | |||
| } | |||
| } | |||
| }); | |||
| t.start(); | |||
| } | |||
| @Override | |||
| public void unavailable(String error) { | |||
| availableAction.setCurrentUploadState(UploadAction.UploadState.ERROR); | |||
| availableAction.setError(error); | |||
| uploadInformation.setCurrentSyncStatus(UploadInformation.SyncStatus.FAILURE); | |||
| Snackbar sb = Snackbar.make(rootLayout, error, Snackbar.LENGTH_INDEFINITE); | |||
| sb.setAction("Retry", new View.OnClickListener() { | |||
| @Override | |||
| public void onClick(View v) { | |||
| availableAction.setError(null); | |||
| availableAction.setCurrentUploadState(UploadAction.UploadState.LOADING); | |||
| startUpload(); | |||
| } | |||
| }); | |||
| sb.show(); | |||
| } | |||
| }; | |||
| private MispRestClient.OrganisationCallback organisationCallback = new MispRestClient.OrganisationCallback() { | |||
| @Override | |||
| public void success(Organisation organisation) { | |||
| orgAction.setCurrentUploadState(UploadAction.UploadState.DONE); | |||
| userAction.setCurrentUploadState(UploadAction.UploadState.LOADING); | |||
| // for later reference in add user callback | |||
| uploadInformation.getRemote().organisation.id = organisation.id; | |||
| restClient.addUser(generateSyncUser(organisation), userCallback); | |||
| } | |||
| @Override | |||
| public void failure(String error) { | |||
| // IF error = org already exists: | |||
| // resClient.addUser() | |||
| orgAction.setCurrentUploadState(UploadAction.UploadState.ERROR); | |||
| uploadInformation.setCurrentSyncStatus(UploadInformation.SyncStatus.FAILURE); | |||
| preferenceManager.addUploadInformation(uploadInformation); | |||
| Snackbar.make(rootLayout, error, Snackbar.LENGTH_LONG).show(); | |||
| } | |||
| }; | |||
| private MispRestClient.UserCallback userCallback = new MispRestClient.UserCallback() { | |||
| @Override | |||
| public void success(User user) { | |||
| userAction.setCurrentUploadState(UploadAction.UploadState.DONE); | |||
| Server server = new Server(); | |||
| server.name = uploadInformation.getRemote().organisation.name + "'s Sync Server"; | |||
| server.url = uploadInformation.getRemote().baseUrl; | |||
| server.remote_org_id = uploadInformation.getRemote().organisation.id; | |||
| server.authkey = uploadInformation.getLocal().syncUserAuthkey; | |||
| server.pull = uploadInformation.isPull(); | |||
| server.push = uploadInformation.isPush(); | |||
| server.caching_enabled = uploadInformation.isCached(); | |||
| server.self_signed = uploadInformation.isAllowSelfSigned(); | |||
| restClient.addServer(server, serverCallback); | |||
| } | |||
| @Override | |||
| public void failure(String error) { | |||
| userAction.setCurrentUploadState(UploadAction.UploadState.ERROR); | |||
| uploadInformation.setCurrentSyncStatus(UploadInformation.SyncStatus.FAILURE); | |||
| preferenceManager.addUploadInformation(uploadInformation); | |||
| Snackbar.make(rootLayout, error, Snackbar.LENGTH_LONG).show(); | |||
| } | |||
| }; | |||
| private MispRestClient.ServerCallback serverCallback = new MispRestClient.ServerCallback() { | |||
| @Override | |||
| public void success(List<MispServer> servers) { | |||
| } | |||
| @Override | |||
| public void success(MispServer server) { | |||
| } | |||
| @Override | |||
| public void success(Server server) { | |||
| serverAction.setCurrentUploadState(UploadAction.UploadState.DONE); | |||
| uploadInformation.setCurrentSyncStatus(UploadInformation.SyncStatus.COMPLETE); | |||
| preferenceManager.addUploadInformation(uploadInformation); | |||
| finish(); | |||
| } | |||
| @Override | |||
| public void failure(String error) { | |||
| serverAction.setCurrentUploadState(UploadAction.UploadState.ERROR); | |||
| uploadInformation.setCurrentSyncStatus(UploadInformation.SyncStatus.FAILURE); | |||
| preferenceManager.addUploadInformation(uploadInformation); | |||
| Snackbar.make(rootLayout, error, Snackbar.LENGTH_LONG).show(); | |||
| } | |||
| }; | |||
| private int organisationExists() throws IOException { | |||
| final UUID uuidToCheck = UUID.fromString(uploadInformation.getRemote().organisation.uuid); | |||
| Organisation[] organisations = restClient.getAllOrganisations(); | |||
| if (organisations != null) { | |||
| for (Organisation organisation : organisations) { | |||
| if (uuidToCheck.compareTo(UUID.fromString(organisation.uuid)) == 0) { | |||
| return organisation.id; | |||
| } | |||
| } | |||
| } | |||
| return -1; | |||
| } | |||
| private int userExists() { | |||
| return -1; | |||
| } | |||
| } | |||
| @@ -1,66 +1,112 @@ | |||
| package lu.circl.mispbump.adapters; | |||
| import android.content.Context; | |||
| import android.content.Intent; | |||
| import android.support.annotation.NonNull; | |||
| import android.support.v7.widget.CardView; | |||
| import android.support.v7.widget.RecyclerView; | |||
| import androidx.annotation.NonNull; | |||
| import androidx.constraintlayout.widget.ConstraintLayout; | |||
| import androidx.core.widget.ImageViewCompat; | |||
| import androidx.recyclerview.widget.RecyclerView; | |||
| import android.content.res.ColorStateList; | |||
| import android.view.LayoutInflater; | |||
| import android.view.View; | |||
| import android.view.ViewGroup; | |||
| import android.widget.ImageButton; | |||
| import android.widget.ImageView; | |||
| import android.widget.LinearLayout; | |||
| import android.widget.TextView; | |||
| import android.widget.Toast; | |||
| import java.util.List; | |||
| import lu.circl.mispbump.R; | |||
| import lu.circl.mispbump.activities.HomeActivity; | |||
| import lu.circl.mispbump.activities.ProfileActivity; | |||
| import lu.circl.mispbump.custom_views.MaterialPreferenceText; | |||
| import lu.circl.mispbump.interfaces.IOnItemClickListener; | |||
| import lu.circl.mispbump.models.UploadInformation; | |||
| public class SyncAdapter extends RecyclerView.Adapter<SyncAdapter.SyncViewHolder> { | |||
| private Context context; | |||
| private List<UploadInformation> uploadInformationList; | |||
| private IOnItemClickListener<UploadInformation> deleteListener, retryListener; | |||
| static class SyncViewHolder extends RecyclerView.ViewHolder { | |||
| MaterialPreferenceText email, password; | |||
| TextView orgName, date; | |||
| ImageView syncStatus; | |||
| ImageButton retry, delete; | |||
| SyncViewHolder(View v, final Context context) { | |||
| ConstraintLayout collapsedContent, expandedContent; | |||
| SyncViewHolder(View v) { | |||
| super(v); | |||
| expandedContent = v.findViewById(R.id.expandedContent); | |||
| expandedContent.setVisibility(View.GONE); | |||
| collapsedContent = v.findViewById(R.id.collapsedContent); | |||
| collapsedContent.setOnClickListener(new View.OnClickListener() { | |||
| @Override | |||
| public void onClick(View v) { | |||
| if (expandedContent.getVisibility() == View.GONE) { | |||
| expandedContent.setVisibility(View.VISIBLE); | |||
| } else { | |||
| expandedContent.setVisibility(View.GONE); | |||
| } | |||
| } | |||
| }); | |||
| orgName = v.findViewById(R.id.orgName); | |||
| date = v.findViewById(R.id.date); | |||
| email = v.findViewById(R.id.email); | |||
| password = v.findViewById(R.id.password); | |||
| syncStatus = v.findViewById(R.id.syncStatus); | |||
| retry = v.findViewById(R.id.retryButton); | |||
| delete = v.findViewById(R.id.deleteButton); | |||
| } | |||
| v.setOnClickListener(new View.OnClickListener() { | |||
| void bindDeleteListener(final UploadInformation item, final IOnItemClickListener<UploadInformation> listener) { | |||
| delete.setOnClickListener(new View.OnClickListener() { | |||
| @Override | |||
| public void onClick(View v) { | |||
| // context.startActivity(new Intent(context, ProfileActivity.class)); | |||
| listener.onItemClick(item); | |||
| } | |||
| }); | |||
| } | |||
| void bindRetryListener(final UploadInformation item, final IOnItemClickListener<UploadInformation> listener) { | |||
| retry.setOnClickListener(new View.OnClickListener() { | |||
| @Override | |||
| public void onClick(View v) { | |||
| listener.onItemClick(item); | |||
| } | |||
| }); | |||
| } | |||
| } | |||
| public SyncAdapter(List<UploadInformation> uploadInformationList, Context context) { | |||
| this.uploadInformationList = uploadInformationList; | |||
| public SyncAdapter(Context context) { | |||
| this.context = context; | |||
| } | |||
| public void setUploadInformationList(List<UploadInformation> uploadInformationList) { | |||
| this.uploadInformationList = uploadInformationList; | |||
| notifyDataSetChanged(); | |||
| } | |||
| public void setOnDeleteClickListener(IOnItemClickListener<UploadInformation> listener) { | |||
| deleteListener = listener; | |||
| } | |||
| public void setOnRetryClickListener(IOnItemClickListener<UploadInformation> listener) { | |||
| retryListener = listener; | |||
| } | |||
| @NonNull | |||
| @Override | |||
| public SyncViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) { | |||
| View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.viewholder_sync_card, viewGroup, false); | |||
| return new SyncViewHolder(v, context); | |||
| View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.viewholder_sync, viewGroup, false); | |||
| return new SyncViewHolder(v); | |||
| } | |||
| @Override | |||
| @@ -68,23 +114,29 @@ public class SyncAdapter extends RecyclerView.Adapter<SyncAdapter.SyncViewHolder | |||
| syncViewHolder.orgName.setText(uploadInformationList.get(i).getRemote().organisation.name); | |||
| syncViewHolder.date.setText(uploadInformationList.get(i).getDateString()); | |||
| syncViewHolder.email.setSubText(uploadInformationList.get(i).getRemote().syncUserEmail); | |||
| syncViewHolder.password.setSubText(uploadInformationList.get(i).getRemote().syncUserPassword); | |||
| switch (uploadInformationList.get(i).getCurrentSyncStatus()) { | |||
| case COMPLETE: | |||
| syncViewHolder.syncStatus.setBackgroundColor(context.getColor(R.color.status_green)); | |||
| ImageViewCompat.setImageTintList(syncViewHolder.syncStatus, ColorStateList.valueOf(context.getColor(R.color.status_green))); | |||
| syncViewHolder.syncStatus.setImageResource(R.drawable.ic_check); | |||
| syncViewHolder.retry.setVisibility(View.GONE); | |||
| break; | |||
| case FAILURE: | |||
| syncViewHolder.syncStatus.setBackgroundColor(context.getColor(R.color.status_red)); | |||
| ImageViewCompat.setImageTintList(syncViewHolder.syncStatus, ColorStateList.valueOf(context.getColor(R.color.status_red))); | |||
| syncViewHolder.syncStatus.setImageResource(R.drawable.ic_error_outline); | |||
| syncViewHolder.retry.setVisibility(View.VISIBLE); | |||
| break; | |||
| case PENDING: | |||
| syncViewHolder.syncStatus.setBackgroundColor(context.getColor(R.color.status_green)); | |||
| syncViewHolder.retry.setVisibility(View.GONE); | |||
| ImageViewCompat.setImageTintList(syncViewHolder.syncStatus, ColorStateList.valueOf(context.getColor(R.color.status_amber))); | |||
| syncViewHolder.syncStatus.setImageResource(R.drawable.ic_error_outline); | |||
| syncViewHolder.retry.setVisibility(View.VISIBLE); | |||
| break; | |||
| } | |||
| syncViewHolder.bindDeleteListener(uploadInformationList.get(i), deleteListener); | |||
| syncViewHolder.bindRetryListener(uploadInformationList.get(i), retryListener); | |||
| } | |||
| @Override | |||
| @@ -1,6 +1,6 @@ | |||
| package lu.circl.mispbump.adapters; | |||
| import android.support.v7.widget.RecyclerView; | |||
| import androidx.recyclerview.widget.RecyclerView; | |||
| import android.view.LayoutInflater; | |||
| import android.view.View; | |||
| import android.view.ViewGroup; | |||
| @@ -1,9 +1,11 @@ | |||
| package lu.circl.mispbump.auxiliary; | |||
| import android.app.Activity; | |||
| import android.app.AlertDialog; | |||
| import android.content.Context; | |||
| import android.content.DialogInterface; | |||
| import android.util.Log; | |||
| import androidx.appcompat.app.AlertDialog; | |||
| import java.security.PublicKey; | |||
| @@ -17,6 +19,39 @@ import lu.circl.mispbump.security.DiffieHellman; | |||
| */ | |||
| public class DialogManager { | |||
| public static void saveAndExitDialog(Context context, final IDialogFeedback callback) { | |||
| final AlertDialog.Builder adb = new AlertDialog.Builder(context); | |||
| adb.setTitle("Save before exit?"); | |||
| adb.setMessage("Saved syncs can be accessed from the main menu."); | |||
| adb.setPositiveButton("Save and Exit", new DialogInterface.OnClickListener() { | |||
| @Override | |||
| public void onClick(DialogInterface dialog, int which) { | |||
| if (callback != null) { | |||
| callback.positive(); | |||
| } | |||
| } | |||
| }); | |||
| adb.setNegativeButton("Exit", new DialogInterface.OnClickListener() { | |||
| @Override | |||
| public void onClick(DialogInterface dialog, int which) { | |||
| if (callback != null) { | |||
| callback.negative(); | |||
| } | |||
| } | |||
| }); | |||
| Activity act = (Activity) context; | |||
| act.runOnUiThread(new Runnable() { | |||
| @Override | |||
| public void run() { | |||
| adb.create().show(); | |||
| } | |||
| }); | |||
| } | |||
| /** | |||
| * Dialog to display a received public key. | |||
| * | |||
| @@ -26,6 +61,7 @@ public class DialogManager { | |||
| */ | |||
| public static void publicKeyDialog(PublicKey publicKey, Context context, final IDialogFeedback callback) { | |||
| final AlertDialog.Builder adb = new AlertDialog.Builder(context); | |||
| adb.setTitle("Public Key"); | |||
| String message = "Algorithm: " + publicKey.getAlgorithm() + "\n" + | |||
| @@ -60,7 +96,9 @@ public class DialogManager { | |||
| * @param callback {@link IDialogFeedback} | |||
| */ | |||
| public static void syncInformationDialog(SyncInformation syncInformation, Context context, final IDialogFeedback callback) { | |||
| final AlertDialog.Builder adb = new AlertDialog.Builder(context); | |||
| adb.setTitle("Sync information received"); | |||
| adb.setMessage(syncInformation.organisation.name); | |||
| adb.setPositiveButton("Accept", new DialogInterface.OnClickListener() { | |||
| @@ -99,6 +137,7 @@ public class DialogManager { | |||
| */ | |||
| public static void confirmProceedDialog(Context context, final IDialogFeedback callback) { | |||
| final AlertDialog.Builder adb = new AlertDialog.Builder(context); | |||
| adb.setTitle("Continue?"); | |||
| adb.setMessage("Only continue if your partner already scanned this QR code"); | |||
| adb.setPositiveButton("Continue", new DialogInterface.OnClickListener() { | |||
| @@ -135,7 +174,7 @@ public class DialogManager { | |||
| */ | |||
| public static void loginHelpDialog(Context context) { | |||
| final AlertDialog.Builder adb = new AlertDialog.Builder(context); | |||
| adb.setTitle(R.string.app_name); | |||
| // adb.setTitle(R.string.app_name); | |||
| adb.setMessage(R.string.login_help_text); | |||
| adb.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { | |||
| @Override | |||
| @@ -154,12 +193,75 @@ public class DialogManager { | |||
| }); | |||
| } | |||
| public static void instanceNotAvailableDialog(Context context, final IDialogFeedback callback) { | |||
| final AlertDialog.Builder adb = new AlertDialog.Builder(context); | |||
| adb.setTitle("MISP not available"); | |||
| adb.setMessage("Your MISP instance is not available. Would you like to save?"); | |||
| adb.setPositiveButton("Retry now", new DialogInterface.OnClickListener() { | |||
| @Override | |||
| public void onClick(DialogInterface dialog, int which) { | |||
| if (callback != null) { | |||
| callback.positive(); | |||
| } | |||
| } | |||
| }); | |||
| adb.setNegativeButton("Save & retry later", new DialogInterface.OnClickListener() { | |||
| @Override | |||
| public void onClick(DialogInterface dialog, int which) { | |||
| if (callback != null) { | |||
| callback.negative(); | |||
| } | |||
| } | |||
| }); | |||
| Activity act = (Activity) context; | |||
| act.runOnUiThread(new Runnable() { | |||
| @Override | |||
| public void run() { | |||
| adb.create().show(); | |||
| } | |||
| }); | |||
| } | |||
| public static void deleteSyncInformationDialog(Context context, final IDialogFeedback callback) { | |||
| final AlertDialog.Builder adb = new AlertDialog.Builder(context); | |||
| adb.setTitle("Delete Sync Information?"); | |||
| adb.setMessage("This sync information will be deleted permanently"); | |||
| adb.setPositiveButton("Delete", new DialogInterface.OnClickListener() { | |||
| @Override | |||
| public void onClick(DialogInterface dialog, int which) { | |||
| if (callback != null) { | |||
| callback.positive(); | |||
| } | |||
| } | |||
| }); | |||
| adb.setNegativeButton("Discard", new DialogInterface.OnClickListener() { | |||
| @Override | |||
| public void onClick(DialogInterface dialog, int which) { | |||
| if (callback != null) { | |||
| callback.negative(); | |||
| } | |||
| } | |||
| }); | |||
| Activity act = (Activity) context; | |||
| act.runOnUiThread(new Runnable() { | |||
| @Override | |||
| public void run() { | |||
| adb.create().show(); | |||
| } | |||
| }); | |||
| } | |||
| /** | |||
| * Interface to give feedback about the user choice in dialogs. | |||
| */ | |||
| public interface IDialogFeedback { | |||
| void positive(); | |||
| void negative(); | |||
| } | |||
| } | |||
| @@ -7,17 +7,13 @@ import android.util.Log; | |||
| import com.google.gson.Gson; | |||
| import com.google.gson.reflect.TypeToken; | |||
| import java.io.IOException; | |||
| import java.lang.reflect.Type; | |||
| import java.security.InvalidAlgorithmParameterException; | |||
| import java.security.InvalidKeyException; | |||
| import java.security.Key; | |||
| import java.security.KeyStore; | |||
| import java.security.KeyStoreException; | |||
| import java.security.NoSuchAlgorithmException; | |||
| import java.security.cert.CertificateException; | |||
| import java.util.ArrayList; | |||
| import java.util.List; | |||
| import java.util.UUID; | |||
| import javax.crypto.BadPaddingException; | |||
| import javax.crypto.IllegalBlockSizeException; | |||
| @@ -28,8 +24,6 @@ import lu.circl.mispbump.restful_client.Organisation; | |||
| import lu.circl.mispbump.restful_client.User; | |||
| import lu.circl.mispbump.security.KeyStoreWrapper; | |||
| import static android.support.constraint.Constraints.TAG; | |||
| public class PreferenceManager { | |||
| private static final String TAG = "PreferenceManager"; | |||
| @@ -328,34 +322,126 @@ public class PreferenceManager { | |||
| } | |||
| public void setUploadInformation(UploadInformation uploadInformation) { | |||
| String storedUploadInfoString = preferences.getString(UPLOAD_INFO, ""); | |||
| public void setUploadInformationList(List<UploadInformation> uploadInformationList) { | |||
| KeyStoreWrapper ksw = new KeyStoreWrapper(KeyStoreWrapper.UPLOAD_INFORMATION_ALIAS); | |||
| try { | |||
| String cipherText = ksw.encrypt(new Gson().toJson(uploadInformationList)); | |||
| SharedPreferences.Editor editor = preferences.edit(); | |||
| editor.putString(UPLOAD_INFO, cipherText); | |||
| editor.apply(); | |||
| } catch (NoSuchPaddingException e) { | |||
| e.printStackTrace(); | |||
| } catch (NoSuchAlgorithmException e) { | |||
| e.printStackTrace(); | |||
| } catch (InvalidKeyException e) { | |||
| e.printStackTrace(); | |||
| } catch (BadPaddingException e) { | |||
| e.printStackTrace(); | |||
| } catch (IllegalBlockSizeException e) { | |||
| e.printStackTrace(); | |||
| } | |||
| } | |||
| public List<UploadInformation> getUploadInformation() { | |||
| KeyStoreWrapper ksw = new KeyStoreWrapper(KeyStoreWrapper.UPLOAD_INFORMATION_ALIAS); | |||
| String storedUploadInfoString = preferences.getString(UPLOAD_INFO, null); | |||
| Type type = new TypeToken<List<UploadInformation>>() {}.getType(); | |||
| List<UploadInformation> dataList; | |||
| if (storedUploadInfoString.isEmpty()) { | |||
| dataList = new ArrayList<>(); | |||
| if (storedUploadInfoString == null) { | |||
| return null; | |||
| } | |||
| try { | |||
| storedUploadInfoString = ksw.decrypt(storedUploadInfoString); | |||
| } catch (NoSuchPaddingException e) { | |||
| e.printStackTrace(); | |||
| } catch (NoSuchAlgorithmException e) { | |||
| e.printStackTrace(); | |||
| } catch (InvalidAlgorithmParameterException e) { | |||
| e.printStackTrace(); | |||
| } catch (InvalidKeyException e) { | |||
| e.printStackTrace(); | |||
| } catch (BadPaddingException e) { | |||
| e.printStackTrace(); | |||
| } catch (IllegalBlockSizeException e) { | |||
| e.printStackTrace(); | |||
| } | |||
| return new Gson().fromJson(storedUploadInfoString, type); | |||
| } | |||
| public void addUploadInformation(UploadInformation uploadInformation) { | |||
| List<UploadInformation> uploadInformationList = getUploadInformation(); | |||
| if (uploadInformationList == null) { | |||
| uploadInformationList = new ArrayList<>(); | |||
| uploadInformationList.add(uploadInformation); | |||
| setUploadInformationList(uploadInformationList); | |||
| } else { | |||
| dataList = new Gson().fromJson(storedUploadInfoString, type); | |||
| // check if upload information already exists | |||
| for (int i = 0; i < uploadInformationList.size(); i++) { | |||
| if (uploadInformationList.get(i).getId().compareTo(uploadInformation.getId()) == 0) { | |||
| uploadInformationList.set(i, uploadInformation); | |||
| setUploadInformationList(uploadInformationList); | |||
| return; | |||
| } | |||
| } | |||
| uploadInformationList.add(uploadInformation); | |||
| setUploadInformationList(uploadInformationList); | |||
| } | |||
| } | |||
| dataList.add(uploadInformation); | |||
| public boolean containsUploadInformation(UUID uuid) { | |||
| List<UploadInformation> uploadInformationList = getUploadInformation(); | |||
| SharedPreferences.Editor editor = preferences.edit(); | |||
| editor.putString(UPLOAD_INFO, new Gson().toJson(dataList)); | |||
| editor.apply(); | |||
| if (uploadInformationList == null) { | |||
| return false; | |||
| } | |||
| for (UploadInformation ui : uploadInformationList) { | |||
| if (ui.getId().compareTo(uuid) == 0) { | |||
| return true; | |||
| } | |||
| } | |||
| return false; | |||
| } | |||
| public List<UploadInformation> getUploadInformation() { | |||
| String storedUploadInfoString = preferences.getString(UPLOAD_INFO, ""); | |||
| public boolean removeUploadInformation(UUID uuid) { | |||
| Log.d("PREFS", "uuid to delete: " + uuid.toString()); | |||
| if (storedUploadInfoString.isEmpty()) { | |||
| return null; | |||
| List<UploadInformation> uploadInformationList = getUploadInformation(); | |||
| for (UploadInformation ui : uploadInformationList) { | |||
| Log.d("PREFS", "checking uuid: " + ui.getId().toString()); | |||
| if (ui.getId().compareTo(uuid) == 0) { | |||
| if (uploadInformationList.size() == 1) { | |||
| clearUploadInformation(); | |||
| } else { | |||
| uploadInformationList.remove(ui); | |||
| setUploadInformationList(uploadInformationList); | |||
| } | |||
| return true; | |||
| } | |||
| } | |||
| Type type = new TypeToken<List<UploadInformation>>() {}.getType(); | |||
| return new Gson().fromJson(storedUploadInfoString, type); | |||
| return false; | |||
| } | |||
| public void clearUploadInformation() { | |||
| KeyStoreWrapper keyStoreWrapper = new KeyStoreWrapper(KeyStoreWrapper.UPLOAD_INFORMATION_ALIAS); | |||
| keyStoreWrapper.deleteStoredKey(); | |||
| SharedPreferences.Editor editor = preferences.edit(); | |||
| editor.remove(UPLOAD_INFO); | |||
| editor.apply(); | |||
| } | |||
| @@ -1,6 +1,8 @@ | |||
| package lu.circl.mispbump.cam; | |||
| import android.Manifest; | |||
| import android.animation.Animator; | |||
| import android.animation.AnimatorListenerAdapter; | |||
| import android.app.Activity; | |||
| import android.app.AlertDialog; | |||
| import android.app.Dialog; | |||
| @@ -8,7 +10,12 @@ import android.content.Context; | |||
| import android.content.DialogInterface; | |||
| import android.content.pm.PackageManager; | |||
| import android.content.res.Configuration; | |||
| import android.graphics.*; | |||
| import android.graphics.Bitmap; | |||
| import android.graphics.ImageFormat; | |||
| import android.graphics.Matrix; | |||
| import android.graphics.Point; | |||
| import android.graphics.RectF; | |||
| import android.graphics.SurfaceTexture; | |||
| import android.hardware.camera2.CameraAccessException; | |||
| import android.hardware.camera2.CameraCaptureSession; | |||
| import android.hardware.camera2.CameraCharacteristics; | |||
| @@ -21,19 +28,28 @@ import android.media.ImageReader; | |||
| import android.os.Bundle; | |||
| import android.os.Handler; | |||
| import android.os.HandlerThread; | |||
| import android.renderscript.*; | |||
| import android.support.annotation.NonNull; | |||
| import android.support.v4.app.ActivityCompat; | |||
| import android.support.v4.app.DialogFragment; | |||
| import android.support.v4.app.Fragment; | |||
| import android.support.v4.content.ContextCompat; | |||
| import android.renderscript.Allocation; | |||
| import android.renderscript.Element; | |||
| import android.renderscript.RenderScript; | |||
| import android.renderscript.ScriptIntrinsicYuvToRGB; | |||
| import android.renderscript.Type; | |||
| import android.util.Log; | |||
| import android.util.Size; | |||
| import android.util.SparseArray; | |||
| import android.util.SparseIntArray; | |||
| import android.view.*; | |||
| import android.view.LayoutInflater; | |||
| import android.view.Surface; | |||
| import android.view.TextureView; | |||
| import android.view.View; | |||
| import android.view.ViewGroup; | |||
| import android.widget.Toast; | |||
| import androidx.annotation.NonNull; | |||
| import androidx.core.app.ActivityCompat; | |||
| import androidx.core.content.ContextCompat; | |||
| import androidx.fragment.app.DialogFragment; | |||
| import androidx.fragment.app.Fragment; | |||
| import com.google.android.gms.vision.Frame; | |||
| import com.google.android.gms.vision.barcode.Barcode; | |||
| import com.google.android.gms.vision.barcode.BarcodeDetector; | |||
| @@ -108,6 +124,10 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest | |||
| } | |||
| } | |||
| private static final String TAG = "CAMERA"; | |||
| private View hideCamView; | |||
| private QrScanCallback qrResultCallback; | |||
| @Override | |||
| @@ -126,8 +146,6 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest | |||
| ORIENTATIONS.append(Surface.ROTATION_270, 180); | |||
| } | |||
| private static final String TAG = "CameraFragment"; | |||
| /** | |||
| * Max preview width that is guaranteed by Camera2 API | |||
| */ | |||
| @@ -163,7 +181,8 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest | |||
| } | |||
| @Override | |||
| public void onSurfaceTextureUpdated(SurfaceTexture texture) { } | |||
| public void onSurfaceTextureUpdated(SurfaceTexture texture) { | |||
| } | |||
| }; | |||
| private ImageProcessingThread imageProcessingThread; | |||
| @@ -338,6 +357,10 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest | |||
| @Override | |||
| public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { | |||
| View v = inflater.inflate(R.layout.fragment_camera, container, false); | |||
| hideCamView = v.findViewById(R.id.hideCam); | |||
| hideCamView.setVisibility(View.GONE); | |||
| initRenderScript(); | |||
| setUpBarcodeDetector(); | |||
| return v; | |||
| @@ -356,20 +379,21 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest | |||
| @Override | |||
| public void onResume() { | |||
| super.onResume(); | |||
| startBackgroundThread(); | |||
| imageProcessingThread = new ImageProcessingThread(); | |||
| imageProcessingThread.start(); | |||
| // When the screen is turned off and turned back on, the SurfaceTexture is already | |||
| // available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open | |||
| // a camera and start preview from here (otherwise, we wait until the surface is ready in | |||
| // the SurfaceTextureListener). | |||
| if (autoFitTextureView.isAvailable()) { | |||
| openCamera(autoFitTextureView.getWidth(), autoFitTextureView.getHeight()); | |||
| } else { | |||
| autoFitTextureView.setSurfaceTextureListener(mSurfaceTextureListener); | |||
| } | |||
| enablePreview(); | |||
| // startBackgroundThread(); | |||
| // | |||
| // imageProcessingThread = new ImageProcessingThread(); | |||
| // imageProcessingThread.start(); | |||
| // | |||
| // // When the screen is turned off and turned back on, the SurfaceTexture is already | |||
| // // available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open | |||
| // // a camera and start preview from here (otherwise, we wait until the surface is ready in | |||
| // // the SurfaceTextureListener). | |||
| // if (autoFitTextureView.isAvailable()) { | |||
| // openCamera(autoFitTextureView.getWidth(), autoFitTextureView.getHeight()); | |||
| // } else { | |||
| // autoFitTextureView.setSurfaceTextureListener(mSurfaceTextureListener); | |||
| // } | |||
| } | |||
| @Override | |||
| @@ -590,8 +614,13 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest | |||
| * Stops the background thread and its {@link Handler}. | |||
| */ | |||
| private void stopBackgroundThread() { | |||
| mBackgroundThread.quitSafely(); | |||
| if (mBackgroundThread == null) { | |||
| return; | |||
| } | |||
| try { | |||
| mBackgroundThread.quitSafely(); | |||
| mBackgroundThread.join(); | |||
| mBackgroundThread = null; | |||
| mBackgroundHandler = null; | |||
| @@ -773,6 +802,11 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest | |||
| void qrScanResult(String qrData); | |||
| } | |||
| public interface CameraReadyCallback { | |||
| void ready(); | |||
| } | |||
| private CameraReadyCallback cameraReadyCallback; | |||
| private boolean readQrEnabled = true; | |||
| private BarcodeDetector barcodeDetector; | |||
| private RenderScript renderScript; | |||
| @@ -826,10 +860,58 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest | |||
| } | |||
| public void setReadQrEnabled(boolean enabled) { | |||
| Log.d(TAG, "setReadQrEnabled() called with: enabled = [" + enabled + "]"); | |||
| readQrEnabled = enabled; | |||
| } | |||
| public void disablePreview() { | |||
| hideCamView.setAlpha(0f); | |||
| hideCamView.setVisibility(View.VISIBLE); | |||
| hideCamView.animate() | |||
| .alpha(1f) | |||
| .setDuration(250) | |||
| .setListener(new AnimatorListenerAdapter() { | |||
| @Override | |||
| public void onAnimationEnd(Animator animation) { | |||
| closeCamera(); | |||
| stopBackgroundThread(); | |||
| if (imageProcessingThread.isAlive()) { | |||
| imageProcessingThread.isRunning = false; | |||
| } | |||
| } | |||
| }); | |||
| } | |||
| public void enablePreview() { | |||
| startBackgroundThread(); | |||
| imageProcessingThread = new ImageProcessingThread(); | |||
| imageProcessingThread.start(); | |||
| if (autoFitTextureView.isAvailable()) { | |||
| openCamera(autoFitTextureView.getWidth(), autoFitTextureView.getHeight()); | |||
| } else { | |||
| autoFitTextureView.setSurfaceTextureListener(mSurfaceTextureListener); | |||
| } | |||
| hideCamView.setAlpha(1f); | |||
| hideCamView.setVisibility(View.VISIBLE); | |||
| hideCamView.animate() | |||
| .alpha(0f) | |||
| .setStartDelay(100) | |||
| .setDuration(1000) | |||
| .setListener(new AnimatorListenerAdapter() { | |||
| @Override | |||
| public void onAnimationEnd(Animator animation) { | |||
| hideCamView.setVisibility(View.GONE); | |||
| if (cameraReadyCallback != null) { | |||
| cameraReadyCallback.ready(); | |||
| } | |||
| } | |||
| }); | |||
| } | |||
| private void setUpBarcodeDetector() { | |||
| barcodeDetector = new BarcodeDetector.Builder(getActivity()) | |||
| .setBarcodeFormats(Barcode.QR_CODE) | |||
| @@ -843,5 +925,9 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest | |||
| public void setOnQrAvailableListener(QrScanCallback callback) { | |||
| qrResultCallback = callback; | |||
| } | |||
| public void setCameraReadyCallback(CameraReadyCallback callback) { | |||
| this.cameraReadyCallback = callback; | |||
| } | |||
| } | |||
| @@ -0,0 +1,58 @@ | |||
| package lu.circl.mispbump.custom_views; | |||
| import android.content.Context; | |||
| import android.util.AttributeSet; | |||
| import android.view.LayoutInflater; | |||
| import android.view.MotionEvent; | |||
| import android.view.View; | |||
| import androidx.coordinatorlayout.widget.CoordinatorLayout; | |||
| import com.google.android.material.bottomsheet.BottomSheetBehavior; | |||
| public class ExtendedBottomSheetBehavior<V extends View> extends BottomSheetBehavior<V> { | |||
| private boolean swipeable = false; | |||
| private Context context; | |||
| public ExtendedBottomSheetBehavior() { | |||
| super(); | |||
| } | |||
| public ExtendedBottomSheetBehavior(Context context, AttributeSet attrs) { | |||
| super(context, attrs); | |||
| this.context = context; | |||
| } | |||
| @Override | |||
| public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) { | |||
| if (swipeable) { | |||
| return super.onInterceptTouchEvent(parent, child, event); | |||
| } | |||
| return false; | |||
| } | |||
| @Override | |||
| public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) { | |||
| if (swipeable) { | |||
| return super.onTouchEvent(parent, child, event); | |||
| } | |||
| return false; | |||
| } | |||
| @Override | |||
| public boolean onNestedPreFling(CoordinatorLayout parent, V child, View target, float velocityX, float velocityY) { | |||
| if (swipeable) { | |||
| return super.onNestedPreFling(parent, child, target, velocityX, velocityY); | |||
| } | |||
| return false; | |||
| } | |||
| public void setSwipeable(boolean swipeable) { | |||
| this.swipeable = swipeable; | |||
| } | |||
| public boolean isSwipeable() { | |||
| return swipeable; | |||
| } | |||
| } | |||
| @@ -2,7 +2,7 @@ package lu.circl.mispbump.custom_views; | |||
| import android.content.Context; | |||
| import android.content.res.TypedArray; | |||
| import android.support.constraint.ConstraintLayout; | |||
| import androidx.constraintlayout.widget.ConstraintLayout; | |||
| import android.util.AttributeSet; | |||
| import android.view.LayoutInflater; | |||
| import android.view.View; | |||
| @@ -39,9 +39,4 @@ public class MaterialPreferenceText extends ConstraintLayout { | |||
| public void setSubText(String subText) { | |||
| subtitle.setText(subText); | |||
| } | |||
| // @Override | |||
| // public void onClick(View v) { | |||
| // | |||
| // } | |||
| } | |||
| @@ -0,0 +1,109 @@ | |||
| package lu.circl.mispbump.custom_views; | |||
| import android.content.Context; | |||
| import android.content.res.ColorStateList; | |||
| import android.content.res.TypedArray; | |||
| import android.util.AttributeSet; | |||
| import android.view.LayoutInflater; | |||
| import android.view.View; | |||
| import android.widget.ImageView; | |||
| import android.widget.ProgressBar; | |||
| import android.widget.TextView; | |||
| import androidx.appcompat.widget.LinearLayoutCompat; | |||
| import androidx.core.widget.ImageViewCompat; | |||
| import lu.circl.mispbump.R; | |||
| public class UploadAction extends LinearLayoutCompat { | |||
| private Context context; | |||
| public enum UploadState { | |||
| PENDING, | |||
| LOADING, | |||
| DONE, | |||
| ERROR | |||
| } | |||
| private TextView errorView; | |||
| private UploadState currentUploadState; | |||
| private ImageView stateView; | |||
| private ProgressBar progressBar; | |||
| public UploadAction(Context context) { | |||
| super(context); | |||
| this.context = context; | |||
| } | |||
| public UploadAction(Context context, AttributeSet attrs) { | |||
| super(context, attrs); | |||
| this.context = context; | |||
| TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.UploadAction); | |||
| String title = a.getString(R.styleable.UploadAction_title); | |||
| a.recycle(); | |||
| LayoutInflater inflater = LayoutInflater.from(context); | |||
| View baseView = inflater.inflate(R.layout.view_upload_action, this); | |||
| errorView = findViewById(R.id.error); | |||
| TextView titleView = baseView.findViewById(R.id.title); | |||
| titleView.setText(title); | |||
| stateView = findViewById(R.id.stateView); | |||
| progressBar = findViewById(R.id.progressBar); | |||
| } | |||
| /** | |||
| * Displays an error message for the upload action. | |||
| * @param error a string to show or null to hide | |||
| */ | |||
| public void setError(String error) { | |||
| if (error == null) { | |||
| errorView.setVisibility(GONE); | |||
| } | |||
| errorView.setText(error); | |||
| errorView.setVisibility(VISIBLE); | |||
| } | |||
| public void setCurrentUploadState(UploadState state) { | |||
| currentUploadState = state; | |||
| progressBar.setVisibility(GONE); | |||
| switch (state) { | |||
| case PENDING: | |||
| stateView.setVisibility(VISIBLE); | |||
| stateView.setImageResource(R.drawable.ic_info_outline); | |||
| ImageViewCompat.setImageTintList(stateView, ColorStateList.valueOf(context.getColor(R.color.status_amber))); | |||
| break; | |||
| case LOADING: | |||
| stateView.setVisibility(GONE); | |||
| progressBar.setVisibility(VISIBLE); | |||
| break; | |||
| case DONE: | |||
| stateView.setVisibility(VISIBLE); | |||
| stateView.setImageResource(R.drawable.ic_check); | |||
| ImageViewCompat.setImageTintList(stateView, ColorStateList.valueOf(context.getColor(R.color.status_green))); | |||
| break; | |||
| case ERROR: | |||
| stateView.setVisibility(VISIBLE); | |||
| stateView.setImageResource(R.drawable.ic_error_outline); | |||
| ImageViewCompat.setImageTintList(stateView, ColorStateList.valueOf(context.getColor(R.color.status_red))); | |||
| break; | |||
| } | |||
| } | |||
| public UploadState getCurrentUploadState() { | |||
| return currentUploadState; | |||
| } | |||
| } | |||
| @@ -1,8 +1,6 @@ | |||
| package lu.circl.mispbump.fragments; | |||
| import android.support.v4.app.Fragment; | |||
| import androidx.fragment.app.Fragment; | |||
| public class HomeFragment extends Fragment { | |||
| } | |||
| @@ -1,9 +1,10 @@ | |||
| package lu.circl.mispbump.fragments; | |||
| import android.os.Bundle; | |||
| import android.support.annotation.NonNull; | |||
| import android.support.design.widget.FloatingActionButton; | |||
| import android.support.v4.app.Fragment; | |||
| import androidx.annotation.NonNull; | |||
| import androidx.annotation.Nullable; | |||
| import androidx.fragment.app.Fragment; | |||
| import android.view.LayoutInflater; | |||
| import android.view.View; | |||
| import android.view.ViewGroup; | |||
| @@ -13,37 +14,17 @@ import lu.circl.mispbump.R; | |||
| public class SyncOptionsFragment extends Fragment { | |||
| private Switch share, push, pull, cache; | |||
| private OptionsReadyCallback readyCallback; | |||
| public interface OptionsReadyCallback { | |||
| void ready(boolean share_events, boolean push, boolean pull, boolean caching); | |||
| } | |||
| public Switch allowSelfSigned, push, pull, cache; | |||
| @Override | |||
| public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { | |||
| View v = inflater.inflate(R.layout.fragment_sync_options, container, false); | |||
| share = v.findViewById(R.id.share_events_switch); | |||
| 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); | |||
| FloatingActionButton fab = v.findViewById(R.id.sync_options_fab); | |||
| fab.setOnClickListener(fabListener); | |||
| return v; | |||
| } | |||
| private View.OnClickListener fabListener = new View.OnClickListener() { | |||
| @Override | |||
| public void onClick(View v) { | |||
| readyCallback.ready(share.isChecked(), push.isChecked(), pull.isChecked(), cache.isChecked()); | |||
| } | |||
| }; | |||
| public void setOnOptionsReadyCallback(OptionsReadyCallback callback) { | |||
| readyCallback = callback; | |||
| } | |||
| } | |||
| @@ -0,0 +1,5 @@ | |||
| package lu.circl.mispbump.interfaces; | |||
| public interface IOnItemClickListener<T> { | |||
| void onItemClick(T clickedObject); | |||
| } | |||
| @@ -9,9 +9,21 @@ import lu.circl.mispbump.restful_client.Organisation; | |||
| public class SyncInformation { | |||
| public Organisation organisation; | |||
| public String syncUserEmail; | |||
| public String syncUserPassword; | |||
| public String syncUserAuthkey; | |||
| public String baseUrl; | |||
| public SyncInformation() {} | |||
| @Override | |||
| public String toString() { | |||
| return "SyncInformation{" + | |||
| "organisation=" + organisation + | |||
| ", syncUserEmail='" + syncUserEmail + '\'' + | |||
| ", syncUserPassword='" + syncUserPassword + '\'' + | |||
| ", syncUserAuthkey='" + syncUserAuthkey + '\'' + | |||
| ", baseUrl='" + baseUrl + '\'' + | |||
| '}'; | |||
| } | |||
| } | |||
| @@ -1,11 +1,15 @@ | |||
| package lu.circl.mispbump.models; | |||
| import androidx.annotation.NonNull; | |||
| import java.io.Serializable; | |||
| import java.text.SimpleDateFormat; | |||
| import java.util.Calendar; | |||
| import java.util.Date; | |||
| import java.util.Locale; | |||
| import java.util.UUID; | |||
| public class UploadInformation { | |||
| public class UploadInformation implements Serializable { | |||
| public enum SyncStatus { | |||
| COMPLETE, | |||
| @@ -13,8 +17,12 @@ public class UploadInformation { | |||
| PENDING | |||
| } | |||
| private UUID id; | |||
| private SyncStatus currentSyncStatus = SyncStatus.PENDING; | |||
| private boolean allowSelfSigned, pull, push, cached; | |||
| private SyncInformation local; | |||
| private SyncInformation remote; | |||
| @@ -29,12 +37,11 @@ public class UploadInformation { | |||
| } | |||
| public UploadInformation(SyncInformation local, SyncInformation remote) { | |||
| id = UUID.randomUUID(); | |||
| date = Calendar.getInstance().getTime(); | |||
| this.local = local; | |||
| this.remote = remote; | |||
| date = Calendar.getInstance().getTime(); | |||
| } | |||
| // getter and setter | |||
| @@ -60,6 +67,16 @@ public class UploadInformation { | |||
| return remote; | |||
| } | |||
| public UUID getId() { | |||
| return id; | |||
| } | |||
| public void setId(UUID id) { | |||
| this.id = id; | |||
| } | |||
| public void setDate() { | |||
| setDate(Calendar.getInstance().getTime()); | |||
| } | |||
| public void setDate(Date date) { | |||
| this.date = date; | |||
| } | |||
| @@ -71,4 +88,42 @@ public class UploadInformation { | |||
| 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 + | |||
| '}'; | |||
| } | |||
| } | |||
| @@ -4,20 +4,19 @@ import android.annotation.SuppressLint; | |||
| import android.content.Context; | |||
| import android.util.Log; | |||
| import com.google.gson.Gson; | |||
| import com.google.gson.GsonBuilder; | |||
| import com.google.gson.JsonObject; | |||
| import org.json.JSONException; | |||
| import org.json.JSONObject; | |||
| import java.io.IOException; | |||
| import java.net.NoRouteToHostException; | |||
| import java.security.cert.CertPathValidatorException; | |||
| import java.security.cert.CertificateException; | |||
| import java.util.Iterator; | |||
| import java.util.List; | |||
| import javax.net.ssl.HostnameVerifier; | |||
| import javax.net.ssl.SSLContext; | |||
| import javax.net.ssl.SSLHandshakeException; | |||
| import javax.net.ssl.SSLSession; | |||
| import javax.net.ssl.SSLSocketFactory; | |||
| import javax.net.ssl.TrustManager; | |||
| @@ -27,7 +26,6 @@ import lu.circl.mispbump.auxiliary.PreferenceManager; | |||
| import okhttp3.Interceptor; | |||
| import okhttp3.OkHttpClient; | |||
| import okhttp3.Request; | |||
| import okhttp3.ResponseBody; | |||
| import okhttp3.logging.HttpLoggingInterceptor; | |||
| import retrofit2.Call; | |||
| import retrofit2.Callback; | |||
| @@ -45,23 +43,34 @@ public class MispRestClient { | |||
| public interface AvailableCallback { | |||
| void available(); | |||
| void unavailable(); | |||
| void unavailable(String error); | |||
| } | |||
| public interface UserCallback { | |||
| void success(User user); | |||
| void failure(String error); | |||
| } | |||
| public interface OrganisationCallback { | |||
| void success(Organisation organisation); | |||
| void failure(String error); | |||
| } | |||
| public interface OrganisationsCallback { | |||
| void success(Organisation[] organisations); | |||
| void failure(String error); | |||
| } | |||
| public interface ServerCallback { | |||
| void success(List<MispServer> servers); | |||
| void success(MispServer server); | |||
| void success(Server server); | |||
| void failure(String error); | |||
| } | |||
| @@ -71,6 +80,7 @@ public class MispRestClient { | |||
| /** | |||
| * Initializes the rest client to communicate with a MISP instance. | |||
| * | |||
| * @param context needed to access the preferences for loading credentials | |||
| */ | |||
| public MispRestClient(Context context) { | |||
| @@ -95,13 +105,15 @@ public class MispRestClient { | |||
| /** | |||
| * NOTE: for development only! | |||
| * Accepts all certificates so self signed certs are also accepted. | |||
| * @return OkHttpClient which accepts all certificates | |||
| * <p> | |||
| * Accepts all certificates including self signed. | |||
| * | |||
| * @return {@link OkHttpClient} which accepts all certificates | |||
| */ | |||
| private OkHttpClient getUnsafeOkHttpClient() { | |||
| try { | |||
| // Create a trust manager that does not validate certificate chains | |||
| final TrustManager[] trustAllCerts = new TrustManager[] { | |||
| final TrustManager[] trustAllCerts = new TrustManager[]{ | |||
| new X509TrustManager() { | |||
| @SuppressLint("TrustAllX509TrustManager") | |||
| @Override | |||
| @@ -163,6 +175,7 @@ public class MispRestClient { | |||
| /** | |||
| * Check via pyMispRoute if server is available | |||
| * | |||
| * @param callback {@link AvailableCallback} | |||
| */ | |||
| public void isAvailable(final AvailableCallback callback) { | |||
| @@ -176,7 +189,7 @@ public class MispRestClient { | |||
| return; | |||
| } | |||
| callback.unavailable(); | |||
| callback.unavailable(extractError(response)); | |||
| } else { | |||
| callback.available(); | |||
| } | |||
| @@ -184,7 +197,7 @@ public class MispRestClient { | |||
| @Override | |||
| public void onFailure(Call<Version> call, Throwable t) { | |||
| callback.unavailable(); | |||
| callback.unavailable(extractError(t)); | |||
| } | |||
| }); | |||
| } | |||
| @@ -193,6 +206,7 @@ public class MispRestClient { | |||
| /** | |||
| * Fetches information about the user that is associated with saved auth key. | |||
| * | |||
| * @param callback {@link UserCallback} wrapper to return user directly | |||
| */ | |||
| public void getMyUser(final UserCallback callback) { | |||
| @@ -201,7 +215,7 @@ public class MispRestClient { | |||
| call.enqueue(new Callback<MispUser>() { | |||
| @Override | |||
| public void onResponse(Call<MispUser> call, Response<MispUser> response) { | |||
| if(!response.isSuccessful()) { | |||
| if (!response.isSuccessful()) { | |||
| callback.failure(extractError(response)); | |||
| } else { | |||
| if (response.body() != null) { | |||
| @@ -222,7 +236,8 @@ public class MispRestClient { | |||
| /** | |||
| * Get an user with specific ID. | |||
| * @param userId user identifier | |||
| * | |||
| * @param userId user identifier | |||
| * @param callback {@link UserCallback} wrapper to return user directly | |||
| */ | |||
| public void getUser(int userId, final UserCallback callback) { | |||
| @@ -231,7 +246,7 @@ public class MispRestClient { | |||
| call.enqueue(new Callback<MispUser>() { | |||
| @Override | |||
| public void onResponse(Call<MispUser> call, Response<MispUser> response) { | |||
| if(!response.isSuccessful()) { | |||
| if (!response.isSuccessful()) { | |||
| callback.failure(extractError(response)); | |||
| } else { | |||
| if (response.body() != null) { | |||
| @@ -252,7 +267,8 @@ public class MispRestClient { | |||
| /** | |||
| * Add a given user to the MISP instance referenced by url in preferences. | |||
| * @param user user to add | |||
| * | |||
| * @param user user to add | |||
| * @param callback {@link UserCallback} wrapper to return the created user directly | |||
| */ | |||
| public void addUser(User user, final UserCallback callback) { | |||
| @@ -261,9 +277,10 @@ public class MispRestClient { | |||
| call.enqueue(new Callback<MispUser>() { | |||
| @Override | |||
| public void onResponse(Call<MispUser> call, Response<MispUser> response) { | |||
| if(!response.isSuccessful()) { | |||
| if (!response.isSuccessful()) { | |||
| callback.failure(extractError(response)); | |||
| } else { | |||
| assert response.body() != null; | |||
| callback.success(response.body().user); | |||
| } | |||
| } | |||
| @@ -280,7 +297,8 @@ public class MispRestClient { | |||
| /** | |||
| * Get an organisation by a given organisation id. | |||
| * @param orgId organisation identifier | |||
| * | |||
| * @param orgId organisation identifier | |||
| * @param callback {@link OrganisationCallback} wrapper to return a organisation directly | |||
| */ | |||
| public void getOrganisation(int orgId, final OrganisationCallback callback) { | |||
| @@ -289,7 +307,7 @@ public class MispRestClient { | |||
| call.enqueue(new Callback<MispOrganisation>() { | |||
| @Override | |||
| public void onResponse(Call<MispOrganisation> call, Response<MispOrganisation> response) { | |||
| if(!response.isSuccessful()) { | |||
| if (!response.isSuccessful()) { | |||
| callback.failure(extractError(response)); | |||
| } else { | |||
| if (response.body() != null) { | |||
| @@ -307,10 +325,52 @@ public class MispRestClient { | |||
| }); | |||
| } | |||
| public Organisation[] getAllOrganisations() throws IOException { | |||
| Call<List<MispOrganisation>> call = mispRestService.getAllOrganisations(); | |||
| Response<List<MispOrganisation>> response = call.execute(); | |||
| List<MispOrganisation> mispOrganisations = response.body(); | |||
| Organisation[] organisations = new Organisation[mispOrganisations.size()]; | |||
| for (int i = 0; i < mispOrganisations.size(); i++) { | |||
| organisations[i] = mispOrganisations.get(i).organisation; | |||
| } | |||
| return organisations; | |||
| // call.enqueue(new Callback<List<MispOrganisation>>() { | |||
| // @Override | |||
| // public void onResponse(Call<List<MispOrganisation>> call, Response<List<MispOrganisation>> response) { | |||
| // if (!response.isSuccessful()) { | |||
| // // TODO handle | |||
| // return; | |||
| // } | |||
| // | |||
| // List<MispOrganisation> mispOrganisations = response.body(); | |||
| // | |||
| // assert mispOrganisations != null; | |||
| // | |||
| // Organisation[] organisations = new Organisation[mispOrganisations.size()]; | |||
| // | |||
| // for (int i = 0; i < mispOrganisations.size(); i++) { | |||
| // organisations[i] = mispOrganisations.get(i).organisation; | |||
| // } | |||
| // | |||
| // callback.success(organisations); | |||
| // } | |||
| // | |||
| // @Override | |||
| // public void onFailure(Call<List<MispOrganisation>> call, Throwable t) { | |||
| // callback.failure(extractError(t)); | |||
| // } | |||
| // }); | |||
| } | |||
| /** | |||
| * Add a given organisation to the MISP instance referenced by url in preferences. | |||
| * | |||
| * @param organisation organisation to add | |||
| * @param callback {@link OrganisationCallback} wrapper to return the created organisation directly | |||
| * @param callback {@link OrganisationCallback} wrapper to return the created organisation directly | |||
| */ | |||
| public void addOrganisation(Organisation organisation, final OrganisationCallback callback) { | |||
| Call<MispOrganisation> call = mispRestService.addOrganisation(organisation); | |||
| @@ -318,9 +378,10 @@ public class MispRestClient { | |||
| call.enqueue(new Callback<MispOrganisation>() { | |||
| @Override | |||
| public void onResponse(Call<MispOrganisation> call, Response<MispOrganisation> response) { | |||
| if(!response.isSuccessful()) { | |||
| if (!response.isSuccessful()) { | |||
| callback.failure(extractError(response)); | |||
| } else { | |||
| assert response.body() != null; | |||
| callback.success(response.body().organisation); | |||
| } | |||
| } | |||
| @@ -336,6 +397,7 @@ public class MispRestClient { | |||
| /** | |||
| * Get all servers on MISP instance. | |||
| * | |||
| * @param callback {@link OrganisationCallback} wrapper to return a list of servers directly | |||
| */ | |||
| public void getServers(final ServerCallback callback) { | |||
| @@ -344,7 +406,7 @@ public class MispRestClient { | |||
| call.enqueue(new Callback<List<MispServer>>() { | |||
| @Override | |||
| public void onResponse(Call<List<MispServer>> call, Response<List<MispServer>> response) { | |||
| if(!response.isSuccessful()) { | |||
| if (!response.isSuccessful()) { | |||
| callback.failure(extractError(response)); | |||
| } else { | |||
| callback.success(response.body()); | |||
| @@ -360,29 +422,10 @@ public class MispRestClient { | |||
| /** | |||
| * Add a server to the MISP instance | |||
| * @param server the server to create | |||
| * | |||
| * @param server the server to create | |||
| * @param callback {@link ServerCallback} wrapper to return the created server directly | |||
| */ | |||
| // public void addServer(MispServer server, final ServerCallback callback) { | |||
| // Call<MispServer> call = mispRestService.addServer(server); | |||
| // | |||
| // call.enqueue(new Callback<MispServer>() { | |||
| // @Override | |||
| // public void onResponse(Call<MispServer> call, Response<MispServer> response) { | |||
| // if(!response.isSuccessful()) { | |||
| // callback.failure(extractError(response)); | |||
| // } else { | |||
| // callback.success(response.body()); | |||
| // } | |||
| // } | |||
| // | |||
| // @Override | |||
| // public void onFailure(Call<MispServer> call, Throwable t) { | |||
| // callback.failure(t.getMessage()); | |||
| // } | |||
| // }); | |||
| // } | |||
| public void addServer(Server server, final ServerCallback callback) { | |||
| Call<Server> call = mispRestService.addServer(server); | |||
| @@ -403,6 +446,14 @@ public class MispRestClient { | |||
| }); | |||
| } | |||
| // error parsing | |||
| /** | |||
| * Converts error {@link Response}s to human readable info. | |||
| * @param response erroneous response | |||
| * @param <T> type of response | |||
| * @return human readable String that describes the error | |||
| */ | |||
| private <T> String extractError(Response<T> response) { | |||
| switch (response.code()) { | |||
| // bad request (malformed) | |||
| @@ -416,9 +467,15 @@ public class MispRestClient { | |||
| // forbidden | |||
| case 403: | |||
| try { | |||
| assert response.errorBody() != null; | |||
| JSONObject jsonError = new JSONObject(response.errorBody().string()); | |||
| String name = jsonError.getString("name") + "\n"; | |||
| if (name.startsWith("Authentication failed")) { | |||
| return "Authentication failed"; | |||
| } | |||
| String reasons = ""; | |||
| JSONObject errorReasons = jsonError.getJSONObject("errors"); | |||
| @@ -437,11 +494,33 @@ public class MispRestClient { | |||
| return "Could not parse (403) error"; | |||
| // not found | |||
| // not found | |||
| case 404: | |||
| return "Not found"; | |||
| } | |||
| return "Unknown error"; | |||
| } | |||
| /** | |||
| * Converts a {@link Throwable} to a human readable error message. | |||
| * @param t throwable | |||
| * @return human readable String that describes the error. | |||
| */ | |||
| private String extractError(Throwable t) { | |||
| if (t.getCause() instanceof CertificateException) { | |||
| return "Trust anchor for certification path not found.\nSelf signed certificates are not supported."; | |||
| } | |||
| if (t instanceof SSLHandshakeException) { | |||
| return "SSL Handshake Error"; | |||
| } | |||
| if (t instanceof NoRouteToHostException) { | |||
| return "Server is not available (no route to host)"; | |||
| } | |||
| return t.getMessage(); | |||
| } | |||
| } | |||
| @@ -34,6 +34,9 @@ public interface MispRestService { | |||
| @GET("organisations/view/{value}") | |||
| Call<MispOrganisation> getOrganisation(@Path("value") int orgId); | |||
| @GET("organisations") | |||
| Call<List<MispOrganisation>> getAllOrganisations(); | |||
| @POST("admin/organisations/add") | |||
| Call<MispOrganisation> addOrganisation(@Body Organisation organisation); | |||
| @@ -5,18 +5,6 @@ package lu.circl.mispbump.restful_client; | |||
| */ | |||
| public class Organisation { | |||
| public Organisation() { | |||
| } | |||
| public Organisation(String name) { | |||
| this.name = name; | |||
| } | |||
| public Organisation(String name, String description) { | |||
| this.name = name; | |||
| this.description = description; | |||
| } | |||
| public Integer id; | |||
| public String name; | |||
| public String date_created; | |||
| @@ -32,7 +20,19 @@ public class Organisation { | |||
| public String created_by; | |||
| public Integer user_count; | |||
| public Organisation syncOrganisation() { | |||
| public Organisation() { | |||
| } | |||
| public Organisation(String name) { | |||
| this.name = name; | |||
| } | |||
| public Organisation(String name, String description) { | |||
| this.name = name; | |||
| this.description = description; | |||
| } | |||
| public Organisation toSyncOrganisation() { | |||
| Organisation organisation = new Organisation(); | |||
| organisation.local = true; | |||
| organisation.name = name; | |||
| @@ -32,6 +32,7 @@ public class DiffieHellman { | |||
| private byte[] sharedSecret; | |||
| private IvParameterSpec ivParameterSpec; | |||
| private DiffieHellman() { | |||
| initialize(); | |||
| } | |||
| @@ -68,25 +69,6 @@ public class DiffieHellman { | |||
| } | |||
| } | |||
| /** | |||
| * Generates a shared secret and derives an initialisation vector from it. | |||
| * @param publickey public key of the sync partner | |||
| */ | |||
| public void setForeignPublicKey(PublicKey publickey) { | |||
| try { | |||
| keyAgreement.doPhase(publickey, true); | |||
| byte[] tmpSharedSecret = keyAgreement.generateSecret(); | |||
| sharedSecret = Arrays.copyOfRange(tmpSharedSecret, 0, 32); | |||
| byte[] inputVector = Arrays.copyOfRange(sharedSecret, 32, 48); | |||
| ivParameterSpec = new IvParameterSpec(inputVector); | |||
| } catch (InvalidKeyException e) { | |||
| e.printStackTrace(); | |||
| } | |||
| } | |||
| /** | |||
| * Encrypts data. | |||
| * @param data data to encrypt | |||
| @@ -129,6 +111,27 @@ public class DiffieHellman { | |||
| return data; | |||
| } | |||
| /** | |||
| * Generates a shared secret and derives an initialisation vector from it. | |||
| * @param pk public key of the sync partner | |||
| */ | |||
| public void setForeignPublicKey(PublicKey pk) { | |||
| try { | |||
| keyAgreement.doPhase(pk, true); | |||
| byte[] tmpSharedSecret = keyAgreement.generateSecret(); | |||
| sharedSecret = Arrays.copyOfRange(tmpSharedSecret, 0, 32); | |||
| byte[] inputVector = Arrays.copyOfRange(sharedSecret, 32, 48); | |||
| ivParameterSpec = new IvParameterSpec(inputVector); | |||
| } catch (InvalidKeyException e) { | |||
| e.printStackTrace(); | |||
| } | |||
| } | |||
| /** | |||
| * @return this devices public key | |||
| */ | |||
| public PublicKey getPublicKey() { | |||
| return publickey; | |||
| } | |||
| @@ -35,6 +35,7 @@ public class KeyStoreWrapper { | |||
| public static final String USER_ORGANISATION_INFO_ALIAS = "ALIAS_USER_ORGANISATION_INFO"; | |||
| public static final String AUTOMATION_ALIAS = "ALIAS_AUTOMATION_KEY"; | |||
| public static final String SERVER_URL_ALIAS = "ALIAS_SERVER_URL"; | |||
| public static final String UPLOAD_INFORMATION_ALIAS = "ALIAS_UPLOAD_INFORMATION"; | |||
| private static final String KEYSTORE_PROVIDER = "AndroidKeyStore"; | |||
| private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding"; | |||
| @@ -174,7 +175,6 @@ public class KeyStoreWrapper { | |||
| cipher.init(Cipher.ENCRYPT_MODE, secretKey); | |||
| byte[] byteData = data.getBytes(StandardCharsets.UTF_8); | |||
| // byte[] byteData = Base64.decode(data, Base64.DEFAULT); | |||
| byte[] combined = getCombinedArray(cipher.getIV(), cipher.doFinal(byteData)); | |||
| return Base64.encodeToString(combined, Base64.NO_WRAP); | |||
| } | |||
| @@ -262,15 +262,12 @@ public class KeyStoreWrapper { | |||
| } | |||
| public class IvAndData { | |||
| public IvAndData() {} | |||
| public IvAndData(byte[] iv, byte[] data) { | |||
| IvAndData(byte[] iv, byte[] data) { | |||
| this.iv = iv; | |||
| this.data = data; | |||
| } | |||
| public byte[] iv; | |||
| public byte[] data; | |||
| byte[] iv; | |||
| byte[] data; | |||
| } | |||
| } | |||
| @@ -0,0 +1,7 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <set xmlns:android="http://schemas.android.com/apk/res/android"> | |||
| <translate android:fromXDelta="50%p" android:toXDelta="0" | |||
| android:duration="@android:integer/config_mediumAnimTime"/> | |||
| <alpha android:fromAlpha="0.0" android:toAlpha="1.0" | |||
| android:duration="@android:integer/config_mediumAnimTime" /> | |||
| </set> | |||
| @@ -0,0 +1,7 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <set xmlns:android="http://schemas.android.com/apk/res/android"> | |||
| <translate android:fromXDelta="0" android:toXDelta="-50%p" | |||
| android:duration="@android:integer/config_mediumAnimTime"/> | |||
| <alpha android:fromAlpha="1.0" android:toAlpha="0.0" | |||
| android:duration="@android:integer/config_mediumAnimTime" /> | |||
| </set> | |||
| @@ -0,0 +1,12 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <set xmlns:android="http://schemas.android.com/apk/res/android" > | |||
| <objectAnimator | |||
| android:interpolator="@android:interpolator/accelerate_cubic" | |||
| android:duration="300" | |||
| android:propertyName="x" | |||
| android:valueFrom="1000" | |||
| android:valueTo="0" | |||
| android:valueType="floatType" /> | |||
| </set> | |||
| @@ -0,0 +1,12 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <set xmlns:android="http://schemas.android.com/apk/res/android" > | |||
| <objectAnimator | |||
| android:interpolator="@android:interpolator/accelerate_cubic" | |||
| android:duration="300" | |||
| android:propertyName="x" | |||
| android:valueFrom="-1000" | |||
| android:valueTo="0" | |||
| android:valueType="floatType" /> | |||
| </set> | |||
| @@ -0,0 +1,11 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <set xmlns:android="http://schemas.android.com/apk/res/android" > | |||
| <objectAnimator | |||
| android:duration="300" | |||
| android:propertyName="x" | |||
| android:valueFrom="0" | |||
| android:valueTo="1000" | |||
| android:valueType="floatType" /> | |||
| </set> | |||
| @@ -0,0 +1,11 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <set xmlns:android="http://schemas.android.com/apk/res/android" > | |||
| <objectAnimator | |||
| android:duration="300" | |||
| android:propertyName="x" | |||
| android:valueFrom="0" | |||
| android:valueTo="-1000" | |||
| android:valueType="floatType" /> | |||
| </set> | |||
| @@ -0,0 +1,9 @@ | |||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | |||
| android:width="24dp" | |||
| android:height="24dp" | |||
| android:viewportWidth="24" | |||
| android:viewportHeight="24"> | |||
| <path | |||
| android:fillColor="#FFF" | |||
| android:pathData="M19,11H7.83l4.88,-4.88c0.39,-0.39 0.39,-1.03 0,-1.42 -0.39,-0.39 -1.02,-0.39 -1.41,0l-6.59,6.59c-0.39,0.39 -0.39,1.02 0,1.41l6.59,6.59c0.39,0.39 1.02,0.39 1.41,0 0.39,-0.39 0.39,-1.02 0,-1.41L7.83,13H19c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1z"/> | |||
| </vector> | |||
| @@ -1,5 +1,9 @@ | |||
| <vector android:height="24dp" android:tint="#FFFFFF" | |||
| android:viewportHeight="24.0" android:viewportWidth="24.0" | |||
| android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> | |||
| <path android:fillColor="#FF000000" android:pathData="M12,4l-1.41,1.41L16.17,11H4v2h12.17l-5.58,5.59L12,20l8,-8z"/> | |||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | |||
| android:width="24dp" | |||
| android:height="24dp" | |||
| android:viewportWidth="24" | |||
| android:viewportHeight="24"> | |||
| <path | |||
| android:fillColor="#FFF" | |||
| android:pathData="M5,13h11.17l-4.88,4.88c-0.39,0.39 -0.39,1.03 0,1.42 0.39,0.39 1.02,0.39 1.41,0l6.59,-6.59c0.39,-0.39 0.39,-1.02 0,-1.41l-6.58,-6.6c-0.39,-0.39 -1.02,-0.39 -1.41,0 -0.39,0.39 -0.39,1.02 0,1.41L16.17,11H5c-0.55,0 -1,0.45 -1,1s0.45,1 1,1z"/> | |||
| </vector> | |||
| @@ -1,5 +1,9 @@ | |||
| <vector android:height="24dp" android:tint="#FFFFFF" | |||
| android:viewportHeight="24.0" android:viewportWidth="24.0" | |||
| android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> | |||
| <path android:fillColor="#FF000000" android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/> | |||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | |||
| android:width="24dp" | |||
| android:height="24dp" | |||
| android:viewportWidth="24" | |||
| android:viewportHeight="24"> | |||
| <path | |||
| android:fillColor="#FFF" | |||
| android:pathData="M9,16.17L5.53,12.7c-0.39,-0.39 -1.02,-0.39 -1.41,0 -0.39,0.39 -0.39,1.02 0,1.41l4.18,4.18c0.39,0.39 1.02,0.39 1.41,0L20.29,7.71c0.39,-0.39 0.39,-1.02 0,-1.41 -0.39,-0.39 -1.02,-0.39 -1.41,0L9,16.17z"/> | |||
| </vector> | |||
| @@ -0,0 +1,9 @@ | |||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | |||
| android:width="24dp" | |||
| android:height="24dp" | |||
| android:viewportWidth="24" | |||
| android:viewportHeight="24"> | |||
| <path | |||
| android:fillColor="#FFF" | |||
| android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM15.88,8.29L10,14.17l-1.88,-1.88c-0.39,-0.39 -1.02,-0.39 -1.41,0 -0.39,0.39 -0.39,1.02 0,1.41l2.59,2.59c0.39,0.39 1.02,0.39 1.41,0L17.3,9.7c0.39,-0.39 0.39,-1.02 0,-1.41 -0.39,-0.39 -1.03,-0.39 -1.42,0z"/> | |||
| </vector> | |||
| @@ -1,5 +1,9 @@ | |||
| <vector android:height="24dp" android:tint="#FFFFFF" | |||
| android:viewportHeight="24.0" android:viewportWidth="24.0" | |||
| android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> | |||
| <path android:fillColor="#FF000000" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/> | |||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | |||
| android:width="24dp" | |||
| android:height="24dp" | |||
| android:viewportWidth="24" | |||
| android:viewportHeight="24"> | |||
| <path | |||
| android:fillColor="#FFF" | |||
| android:pathData="M18.3,5.71c-0.39,-0.39 -1.02,-0.39 -1.41,0L12,10.59 7.11,5.7c-0.39,-0.39 -1.02,-0.39 -1.41,0 -0.39,0.39 -0.39,1.02 0,1.41L10.59,12 5.7,16.89c-0.39,0.39 -0.39,1.02 0,1.41 0.39,0.39 1.02,0.39 1.41,0L12,13.41l4.89,4.89c0.39,0.39 1.02,0.39 1.41,0 0.39,-0.39 0.39,-1.02 0,-1.41L13.41,12l4.89,-4.89c0.38,-0.38 0.38,-1.02 0,-1.4z"/> | |||
| </vector> | |||
| @@ -0,0 +1,5 @@ | |||
| <vector android:height="24dp" android:tint="#FFFFFF" | |||
| android:viewportHeight="24.0" android:viewportWidth="24.0" | |||
| android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> | |||
| <path android:fillColor="#FF000000" android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM14,13v4h-4v-4H7l5,-5 5,5h-3z"/> | |||
| </vector> | |||
| @@ -1,5 +1,9 @@ | |||
| <vector android:height="24dp" android:tint="#B4B4B4" | |||
| android:viewportHeight="24.0" android:viewportWidth="24.0" | |||
| android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> | |||
| <path android:fillColor="#FF000000" android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2L18,7L6,7v12zM8.46,11.88l1.41,-1.41L12,12.59l2.12,-2.12 1.41,1.41L13.41,14l2.12,2.12 -1.41,1.41L12,15.41l-2.12,2.12 -1.41,-1.41L10.59,14l-2.13,-2.12zM15.5,4l-1,-1h-5l-1,1L5,4v2h14L19,4z"/> | |||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | |||
| android:width="24dp" | |||
| android:height="24dp" | |||
| android:viewportWidth="24" | |||
| android:viewportHeight="24"> | |||
| <path | |||
| android:fillColor="#FF000000" | |||
| android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2L18,9c0,-1.1 -0.9,-2 -2,-2L8,7c-1.1,0 -2,0.9 -2,2v10zM9.17,11.17c0.39,-0.39 1.02,-0.39 1.41,0L12,12.59l1.42,-1.42c0.39,-0.39 1.02,-0.39 1.41,0 0.39,0.39 0.39,1.02 0,1.41L13.41,14l1.42,1.42c0.39,0.39 0.39,1.02 0,1.41 -0.39,0.39 -1.02,0.39 -1.41,0L12,15.41l-1.42,1.42c-0.39,0.39 -1.02,0.39 -1.41,0 -0.39,-0.39 -0.39,-1.02 0,-1.41L10.59,14l-1.42,-1.42c-0.39,-0.38 -0.39,-1.02 0,-1.41zM15.5,4l-0.71,-0.71c-0.18,-0.18 -0.44,-0.29 -0.7,-0.29L9.91,3c-0.26,0 -0.52,0.11 -0.7,0.29L8.5,4L6,4c-0.55,0 -1,0.45 -1,1s0.45,1 1,1h12c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1h-2.5z"/> | |||
| </vector> | |||
| @@ -0,0 +1,9 @@ | |||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | |||
| android:width="24dp" | |||
| android:height="24dp" | |||
| android:viewportWidth="24" | |||
| android:viewportHeight="24"> | |||
| <path | |||
| android:fillColor="#FFF" | |||
| android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,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> | |||
| @@ -1,5 +1,5 @@ | |||
| <vector android:height="24dp" android:tint="#FFFFFF" | |||
| android:viewportHeight="24.0" android:viewportWidth="24.0" | |||
| android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> | |||
| <path android:fillColor="#FF000000" android:pathData="M11,18h2v-2h-2v2zM12,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,8zM12,6c-2.21,0 -4,1.79 -4,4h2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2c0,2 -3,1.75 -3,5h2c0,-2.25 3,-2.5 3,-5 0,-2.21 -1.79,-4 -4,-4z"/> | |||
| <path android:fillColor="#FF000000" android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/> | |||
| </vector> | |||
| @@ -0,0 +1,5 @@ | |||
| <vector android:height="24dp" android:tint="#FFFFFF" | |||
| android:viewportHeight="24.0" android:viewportWidth="24.0" | |||
| android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> | |||
| <path android:fillColor="#FF000000" android:pathData="M12.65,10C11.83,7.67 9.61,6 7,6c-3.31,0 -6,2.69 -6,6s2.69,6 6,6c2.61,0 4.83,-1.67 5.65,-4H17v4h4v-4h2v-4H12.65zM7,14c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z"/> | |||
| </vector> | |||
| @@ -0,0 +1,5 @@ | |||
| <vector android:height="24dp" android:tint="#FFFFFF" | |||
| android:viewportHeight="24.0" android:viewportWidth="24.0" | |||
| android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> | |||
| <path android:fillColor="#FF000000" android:pathData="M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z"/> | |||
| </vector> | |||
| @@ -0,0 +1,5 @@ | |||
| <vector android:height="24dp" android:tint="#FFFFFF" | |||
| android:viewportHeight="24.0" android:viewportWidth="24.0" | |||
| android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> | |||
| <path android:fillColor="#FF000000" android:pathData="M19,4L5,4c-1.11,0 -2,0.9 -2,2v12c0,1.1 0.89,2 2,2h4v-2L5,18L5,8h14v10h-4v2h4c1.1,0 2,-0.9 2,-2L21,6c0,-1.1 -0.89,-2 -2,-2zM12,10l-4,4h3v6h2v-6h3l-4,-4z"/> | |||
| </vector> | |||
| @@ -1,5 +1,9 @@ | |||
| <vector android:height="24dp" android:tint="#FFFFFF" | |||
| android:viewportHeight="24.0" android:viewportWidth="24.0" | |||
| android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> | |||
| <path android:fillColor="#FF000000" android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/> | |||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | |||
| android:width="24dp" | |||
| android:height="24dp" | |||
| android:viewportWidth="24" | |||
| android:viewportHeight="24"> | |||
| <path | |||
| android:fillColor="#FFF" | |||
| android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v1c0,0.55 0.45,1 1,1h14c0.55,0 1,-0.45 1,-1v-1c0,-2.66 -5.33,-4 -8,-4z"/> | |||
| </vector> | |||
| @@ -0,0 +1,9 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | |||
| <shape xmlns:android="http://schemas.android.com/apk/res/android"> | |||
| <solid | |||
| android:color="@color/white"/> | |||
| <corners | |||
| android:topLeftRadius="10dp" | |||
| android:topRightRadius="10dp"/> | |||
| </shape> | |||
| @@ -1,27 +1,25 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <android.support.design.widget.CoordinatorLayout | |||
| <androidx.coordinatorlayout.widget.CoordinatorLayout | |||
| xmlns:android="http://schemas.android.com/apk/res/android" | |||
| xmlns:app="http://schemas.android.com/apk/res-auto" | |||
| xmlns:tools="http://schemas.android.com/tools" | |||
| android:id="@+id/rootLayout" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent"> | |||
| <android.support.design.widget.AppBarLayout | |||
| <com.google.android.material.appbar.AppBarLayout | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content"> | |||
| <android.support.v7.widget.Toolbar | |||
| <androidx.appcompat.widget.Toolbar | |||
| android:id="@+id/toolbar" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="?attr/actionBarSize" | |||
| android:background="?attr/colorPrimary" | |||
| android:elevation="4dp" | |||
| android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" | |||
| app:popupTheme="@style/ThemeOverlay.AppCompat.Light" /> | |||
| </android.support.design.widget.AppBarLayout> | |||
| app:popupTheme="@style/ThemeOverlay.AppCompat.Light" | |||
| android:background="@color/colorPrimary" /> | |||
| </com.google.android.material.appbar.AppBarLayout> | |||
| <android.support.v7.widget.RecyclerView | |||
| <androidx.recyclerview.widget.RecyclerView | |||
| android:id="@+id/recyclerView" | |||
| app:layout_behavior="@string/appbar_scrolling_view_behavior" | |||
| android:layout_width="match_parent" | |||
| @@ -32,15 +30,18 @@ | |||
| app:layout_behavior="@string/appbar_scrolling_view_behavior" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent" | |||
| android:drawableTop="@drawable/ic_sync_black_24dp" | |||
| android:gravity="center" | |||
| android:text="@string/no_syncs_hint"/> | |||
| <android.support.design.widget.FloatingActionButton | |||
| <com.google.android.material.floatingactionbutton.FloatingActionButton | |||
| android:id="@+id/home_fab" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_gravity="bottom|end" | |||
| android:layout_margin="16dp" | |||
| android:src="@drawable/ic_qrcode_scan" /> | |||
| app:backgroundTint="@color/colorAccent" | |||
| android:tint="@color/white" | |||
| android:src="@drawable/ic_qrcode_scan"/> | |||
| </android.support.design.widget.CoordinatorLayout> | |||
| </androidx.coordinatorlayout.widget.CoordinatorLayout> | |||
| @@ -1,78 +1,81 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <android.support.constraint.ConstraintLayout | |||
| android:id="@+id/rootLayout" | |||
| xmlns:android="http://schemas.android.com/apk/res/android" | |||
| <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | |||
| xmlns:app="http://schemas.android.com/apk/res-auto" | |||
| xmlns:tools="http://schemas.android.com/tools" | |||
| android:id="@+id/rootLayout" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent" | |||
| tools:context=".activities.LoginActivity"> | |||
| <android.support.v7.widget.Toolbar | |||
| <androidx.appcompat.widget.Toolbar | |||
| android:id="@+id/appbar" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="?attr/actionBarSize" | |||
| android:background="?attr/colorPrimary" | |||
| android:elevation="4dp" | |||
| android:background="@color/colorPrimary" | |||
| android:elevation="6dp" | |||
| android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" | |||
| app:popupTheme="@style/ThemeOverlay.AppCompat.Light" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| app:layout_constraintLeft_toLeftOf="parent" | |||
| app:layout_constraintRight_toRightOf="parent" | |||
| app:layout_constraintLeft_toLeftOf="parent"/> | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| app:popupTheme="@style/ThemeOverlay.AppCompat.Dark" /> | |||
| <android.support.design.widget.TextInputLayout | |||
| <com.google.android.material.textfield.TextInputLayout | |||
| android:id="@+id/login_server_url" | |||
| style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" | |||
| android:layout_width="0dp" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginStart="16dp" | |||
| android:layout_marginEnd="16dp" | |||
| android:layout_marginBottom="8dp" | |||
| android:gravity="center" | |||
| app:layout_constraintBottom_toTopOf="@+id/login_automation_key" | |||
| app:layout_constraintBottom_toTopOf="@+id/guideline" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintHorizontal_bias="0.0" | |||
| app:layout_constraintStart_toStartOf="parent"> | |||
| <android.support.design.widget.TextInputEditText | |||
| <com.google.android.material.textfield.TextInputEditText | |||
| android:id="@+id/login_server_url_edit" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:hint="@string/misp_server_url_hint" | |||
| android:inputType="textUri" /> | |||
| </android.support.design.widget.TextInputLayout> | |||
| </com.google.android.material.textfield.TextInputLayout> | |||
| <android.support.design.widget.TextInputLayout | |||
| <com.google.android.material.textfield.TextInputLayout | |||
| android:id="@+id/login_automation_key" | |||
| style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" | |||
| android:layout_width="0dp" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginStart="16dp" | |||
| android:layout_marginTop="8dp" | |||
| android:layout_marginEnd="16dp" | |||
| android:gravity="center" | |||
| app:layout_constraintBottom_toTopOf="parent" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintHorizontal_bias="0.0" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toBottomOf="parent" | |||
| app:layout_constraintTop_toTopOf="@+id/guideline" | |||
| app:passwordToggleEnabled="true"> | |||
| <android.support.design.widget.TextInputEditText | |||
| <com.google.android.material.textfield.TextInputEditText | |||
| android:id="@+id/login_automation_key_edit" | |||
| style="@style/Base.Widget.MaterialComponents.TextInputEditText" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:hint="@string/misp_automation_hint" | |||
| android:inputType="textPassword" /> | |||
| </android.support.design.widget.TextInputLayout> | |||
| </com.google.android.material.textfield.TextInputLayout> | |||
| <Button | |||
| <com.google.android.material.button.MaterialButton | |||
| android:id="@+id/login_download_button" | |||
| style="@style/Widget.AppCompat.Button.Colored" | |||
| android:layout_width="0dp" | |||
| style="@style/Widget.MaterialComponents.Button.UnelevatedButton" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginStart="16dp" | |||
| android:layout_marginTop="16dp" | |||
| android:layout_marginTop="32dp" | |||
| android:layout_marginEnd="16dp" | |||
| android:gravity="center" | |||
| android:text="@string/download_misp_infos" | |||
| android:text="@string/login" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintHorizontal_bias="0.0" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toBottomOf="@+id/login_automation_key" /> | |||
| @@ -91,5 +94,12 @@ | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toBottomOf="@+id/appbar" /> | |||
| <androidx.constraintlayout.widget.Guideline | |||
| android:id="@+id/guideline" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:orientation="horizontal" | |||
| app:layout_constraintGuide_percent="0.5" /> | |||
| </android.support.constraint.ConstraintLayout> | |||
| </androidx.constraintlayout.widget.ConstraintLayout> | |||
| @@ -0,0 +1,21 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | |||
| xmlns:app="http://schemas.android.com/apk/res-auto" | |||
| xmlns:tools="http://schemas.android.com/tools" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent" | |||
| tools:context=".activities.PreferenceActivity"> | |||
| <com.google.android.material.button.MaterialButton | |||
| android:id="@+id/deleteSyncs" | |||
| android:layout_width="0dp" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginStart="16dp" | |||
| android:layout_marginTop="16dp" | |||
| android:layout_marginEnd="16dp" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| android:text="Delete Syncs"/> | |||
| </androidx.constraintlayout.widget.ConstraintLayout> | |||
| @@ -1,19 +1,18 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" | |||
| <androidx.coordinatorlayout.widget.CoordinatorLayout | |||
| xmlns:android="http://schemas.android.com/apk/res/android" | |||
| xmlns:app="http://schemas.android.com/apk/res-auto" | |||
| xmlns:tools="http://schemas.android.com/tools" | |||
| android:id="@+id/rootLayout" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent" | |||
| android:clipToPadding="false" | |||
| tools:context=".activities.ProfileActivity"> | |||
| <android.support.design.widget.AppBarLayout | |||
| <com.google.android.material.appbar.AppBarLayout | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:fitsSystemWindows="true"> | |||
| android:layout_height="wrap_content"> | |||
| <android.support.constraint.ConstraintLayout | |||
| <androidx.constraintlayout.widget.ConstraintLayout | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent"> | |||
| @@ -57,22 +56,22 @@ | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toBottomOf="@+id/circle" /> | |||
| <android.support.v7.widget.Toolbar | |||
| <androidx.appcompat.widget.Toolbar | |||
| android:id="@+id/toolbar" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="?attr/actionBarSize" | |||
| android:layout_marginTop="24dp" | |||
| android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" | |||
| app:popupTheme="@style/ThemeOverlay.AppCompat.Light" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| app:popupTheme="@style/ThemeOverlay.AppCompat.Light" | |||
| app:titleTextColor="@color/white" /> | |||
| </android.support.constraint.ConstraintLayout> | |||
| </android.support.design.widget.AppBarLayout> | |||
| </androidx.constraintlayout.widget.ConstraintLayout> | |||
| </com.google.android.material.appbar.AppBarLayout> | |||
| <android.support.constraint.ConstraintLayout | |||
| <androidx.constraintlayout.widget.ConstraintLayout | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent" | |||
| app:layout_behavior="@string/appbar_scrolling_view_behavior"> | |||
| @@ -86,7 +85,7 @@ | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toBottomOf="@+id/sector" | |||
| app:pref_icon="@drawable/ic_description" | |||
| app:subText="Hier steht ein sehr langer Text. Er kann sowohl mehrere Zeilen haben als auch keine Ahnung was noch. Aber auf jeden Fall viel Text." | |||
| app:subText="" | |||
| app:text="Description"> | |||
| </lu.circl.mispbump.custom_views.MaterialPreferenceText> | |||
| @@ -100,7 +99,7 @@ | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| app:pref_icon="@drawable/ic_code_black_24dp" | |||
| app:subText="8464-4546546-8464684-654654" | |||
| app:subText="" | |||
| app:text="UUID" /> | |||
| <lu.circl.mispbump.custom_views.MaterialPreferenceText | |||
| @@ -131,14 +130,16 @@ | |||
| </lu.circl.mispbump.custom_views.MaterialPreferenceText> | |||
| </android.support.constraint.ConstraintLayout> | |||
| </androidx.constraintlayout.widget.ConstraintLayout> | |||
| <android.support.design.widget.FloatingActionButton | |||
| <com.google.android.material.floatingactionbutton.FloatingActionButton | |||
| android:id="@+id/fab" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_gravity="bottom|end" | |||
| android:layout_margin="16dp" | |||
| android:src="@drawable/ic_sync_black_24dp" /> | |||
| android:backgroundTint="@color/colorAccent" | |||
| android:src="@drawable/ic_sync_black_24dp" | |||
| android:tint="@color/white" /> | |||
| </android.support.design.widget.CoordinatorLayout> | |||
| </androidx.coordinatorlayout.widget.CoordinatorLayout> | |||
| @@ -1,9 +0,0 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | |||
| xmlns:app="http://schemas.android.com/apk/res-auto" | |||
| xmlns:tools="http://schemas.android.com/tools" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent" | |||
| tools:context=".activities.StartUpActivity"> | |||
| </android.support.constraint.ConstraintLayout> | |||
| @@ -1,40 +1,15 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <android.support.design.widget.CoordinatorLayout | |||
| <androidx.coordinatorlayout.widget.CoordinatorLayout | |||
| xmlns:android="http://schemas.android.com/apk/res/android" | |||
| xmlns:app="http://schemas.android.com/apk/res-auto" | |||
| xmlns:tools="http://schemas.android.com/tools" | |||
| xmlns:android="http://schemas.android.com/apk/res/android" | |||
| android:id="@+id/rootLayout" | |||
| tools:context=".activities.SyncActivity" | |||
| android:layout_height="match_parent" | |||
| android:layout_width="match_parent"> | |||
| <android.support.v7.widget.Toolbar | |||
| android:id="@+id/appbar" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="?attr/actionBarSize" | |||
| android:background="?attr/colorPrimary" | |||
| android:elevation="4dp" | |||
| android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" | |||
| app:layout_constraintLeft_toLeftOf="parent" | |||
| app:layout_constraintRight_toRightOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| app:popupTheme="@style/ThemeOverlay.AppCompat.Light" /> | |||
| <android.support.design.widget.FloatingActionButton | |||
| android:id="@+id/continue_fab" | |||
| android:layout_gravity="bottom|end" | |||
| android:layout_margin="16dp" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:clickable="true" | |||
| android:focusable="true" | |||
| app:srcCompat="@drawable/ic_check" /> | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent"> | |||
| <android.support.constraint.ConstraintLayout | |||
| <androidx.constraintlayout.widget.ConstraintLayout | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent" | |||
| android:layout_marginTop="?attr/actionBarSize"> | |||
| android:layout_height="match_parent"> | |||
| <FrameLayout | |||
| android:id="@+id/sync_fragment_container" | |||
| @@ -43,16 +18,79 @@ | |||
| app:layout_constraintBottom_toBottomOf="parent" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" /> | |||
| app:layout_constraintTop_toTopOf="parent" > | |||
| </FrameLayout> | |||
| <ImageView | |||
| android:id="@+id/qrcode" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginBottom="64dp" | |||
| android:contentDescription="@string/qr_code" | |||
| app:layout_constraintBottom_toBottomOf="parent" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" /> | |||
| </android.support.constraint.ConstraintLayout> | |||
| </android.support.design.widget.CoordinatorLayout> | |||
| </androidx.constraintlayout.widget.ConstraintLayout> | |||
| <LinearLayout | |||
| app:layout_behavior=".custom_views.ExtendedBottomSheetBehavior" | |||
| android:id="@+id/bottomSheet" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:orientation="vertical" | |||
| android:background="@drawable/rounded_rect" | |||
| android:backgroundTint="@color/colorPrimary" | |||
| app:behavior_peekHeight="64dp"> | |||
| <LinearLayout | |||
| android:orientation="horizontal" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content"> | |||
| <ImageButton | |||
| android:id="@+id/prevButton" | |||
| android:layout_width="64dp" | |||
| android:layout_height="64dp" | |||
| android:background="?attr/selectableItemBackground" | |||
| android:padding="8dp" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| app:srcCompat="@drawable/ic_arrow_back" /> | |||
| <ImageView | |||
| android:visibility="invisible" | |||
| android:id="@+id/bottomSheetIcon" | |||
| android:layout_weight="1" | |||
| android:layout_width="0dp" | |||
| android:layout_height="32dp" | |||
| android:layout_gravity="center_vertical" | |||
| android:src="@drawable/ic_check_outline" /> | |||
| <ImageButton | |||
| android:id="@+id/nextButton" | |||
| android:layout_width="64dp" | |||
| android:layout_height="64dp" | |||
| android:background="?attr/selectableItemBackground" | |||
| android:padding="8dp" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| app:srcCompat="@drawable/ic_arrow_forward" /> | |||
| </LinearLayout> | |||
| <TextView | |||
| android:id="@+id/bottomSheetText" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:paddingStart="16dp" | |||
| android:paddingEnd="16dp" | |||
| android:paddingBottom="16dp" | |||
| android:gravity="top" | |||
| android:layout_gravity="center" | |||
| android:text="Received public key from partner" | |||
| android:textColor="@color/white" /> | |||
| </LinearLayout> | |||
| </androidx.coordinatorlayout.widget.CoordinatorLayout> | |||
| @@ -1,51 +0,0 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <android.support.design.widget.CoordinatorLayout | |||
| xmlns:android="http://schemas.android.com/apk/res/android" | |||
| xmlns:app="http://schemas.android.com/apk/res-auto" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent"> | |||
| <android.support.constraint.ConstraintLayout | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent"> | |||
| <FrameLayout | |||
| android:id="@+id/sync_fragment_container" | |||
| android:layout_width="0dp" | |||
| android:layout_height="0dp" | |||
| app:layout_constraintBottom_toBottomOf="parent" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" /> | |||
| <ImageView | |||
| android:id="@+id/qrcode" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:contentDescription="@string/qr_code" | |||
| app:layout_constraintBottom_toBottomOf="parent" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" /> | |||
| </android.support.constraint.ConstraintLayout> | |||
| <!--<android.support.design.bottomappbar.BottomAppBar--> | |||
| <!--style="@style/Widget.MaterialComponents.BottomAppBar.Colored"--> | |||
| <!--android:id="@+id/bottomNavigation"--> | |||
| <!--android:layout_width="match_parent"--> | |||
| <!--android:layout_height="56dp"--> | |||
| <!--android:layout_gravity="bottom"--> | |||
| <!--app:navigationIcon="@drawable/ic_close"--> | |||
| <!--app:fabAlignmentMode="center" />--> | |||
| <!--<android.support.design.widget.FloatingActionButton--> | |||
| <!--android:id="@+id/fab"--> | |||
| <!--android:layout_width="wrap_content"--> | |||
| <!--android:layout_height="wrap_content"--> | |||
| <!--app:borderWidth="0dp"--> | |||
| <!--android:clickable="true"--> | |||
| <!--android:focusable="true"--> | |||
| <!--app:fabSize="normal"--> | |||
| <!--app:layout_anchor="@id/bottomNavigation"--> | |||
| <!--app:srcCompat="@drawable/ic_arrow_forward"/>--> | |||
| </android.support.design.widget.CoordinatorLayout> | |||
| @@ -0,0 +1,90 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <androidx.coordinatorlayout.widget.CoordinatorLayout | |||
| xmlns:android="http://schemas.android.com/apk/res/android" | |||
| xmlns:app="http://schemas.android.com/apk/res-auto" | |||
| xmlns:tools="http://schemas.android.com/tools" | |||
| android:id="@+id/rootLayout" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent" | |||
| tools:context=".activities.UploadActivity"> | |||
| <com.google.android.material.appbar.AppBarLayout | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content"> | |||
| <androidx.appcompat.widget.Toolbar | |||
| android:id="@+id/toolbar" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="?attr/actionBarSize" | |||
| android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" | |||
| app:popupTheme="@style/ThemeOverlay.AppCompat.Light" | |||
| android:background="@color/colorPrimary" /> | |||
| </com.google.android.material.appbar.AppBarLayout> | |||
| <androidx.appcompat.widget.LinearLayoutCompat | |||
| app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" | |||
| android:orientation="vertical" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent"> | |||
| <lu.circl.mispbump.custom_views.UploadAction | |||
| android:id="@+id/availableAction" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginStart="16dp" | |||
| android:layout_marginTop="16dp" | |||
| android:layout_marginEnd="16dp" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| app:title="Check if instance is available" /> | |||
| <lu.circl.mispbump.custom_views.UploadAction | |||
| android:id="@+id/orgAction" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginStart="16dp" | |||
| android:layout_marginTop="16dp" | |||
| android:layout_marginEnd="16dp" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| app:title="Add organisation" /> | |||
| <lu.circl.mispbump.custom_views.UploadAction | |||
| android:id="@+id/userAction" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginStart="16dp" | |||
| android:layout_marginTop="16dp" | |||
| android:layout_marginEnd="16dp" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| app:title="Add sync user" /> | |||
| <lu.circl.mispbump.custom_views.UploadAction | |||
| android:id="@+id/serverAction" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginStart="16dp" | |||
| android:layout_marginTop="16dp" | |||
| android:layout_marginEnd="16dp" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| app:title="Add sync server" /> | |||
| </androidx.appcompat.widget.LinearLayoutCompat> | |||
| <com.google.android.material.floatingactionbutton.FloatingActionButton | |||
| android:id="@+id/fab" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_gravity="bottom|end" | |||
| android:layout_margin="16dp" | |||
| app:backgroundTint="@color/colorAccent" | |||
| android:tint="@color/white" | |||
| android:src="@drawable/ic_cloud_upload"/> | |||
| </androidx.coordinatorlayout.widget.CoordinatorLayout> | |||
| @@ -0,0 +1,13 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <LinearLayout | |||
| xmlns:android="http://schemas.android.com/apk/res/android" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent" | |||
| android:orientation="vertical"> | |||
| <TextView | |||
| android:text="HALLO HER STEHT TEXT" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" /> | |||
| </LinearLayout> | |||
| @@ -0,0 +1,23 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <LinearLayout | |||
| xmlns:android="http://schemas.android.com/apk/res/android" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent" | |||
| android:orientation="vertical" | |||
| android:background="@color/colorPrimary"> | |||
| <ImageView | |||
| android:layout_gravity="center_horizontal" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:src="@drawable/ic_check_outline" | |||
| android:tint="@color/white"/> | |||
| <TextView | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:textAppearance="@style/Text.Title" | |||
| android:textColor="@color/white" | |||
| android:layout_gravity="center_horizontal" | |||
| android:text="Organisation A"/> | |||
| </LinearLayout> | |||
| @@ -1,6 +1,6 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <android.support.constraint.ConstraintLayout | |||
| <androidx.constraintlayout.widget.ConstraintLayout | |||
| xmlns:android="http://schemas.android.com/apk/res/android" | |||
| xmlns:app="http://schemas.android.com/apk/res-auto" | |||
| android:layout_width="match_parent" | |||
| @@ -15,4 +15,14 @@ | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintBottom_toBottomOf="parent"/> | |||
| </android.support.constraint.ConstraintLayout> | |||
| <View | |||
| android:id="@+id/hideCam" | |||
| android:layout_width="0dp" | |||
| android:layout_height="0dp" | |||
| android:background="@color/white" | |||
| app:layout_constraintBottom_toBottomOf="parent" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" /> | |||
| </androidx.constraintlayout.widget.ConstraintLayout> | |||
| @@ -1,24 +1,21 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | |||
| <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | |||
| xmlns:app="http://schemas.android.com/apk/res-auto" | |||
| xmlns:tools="http://schemas.android.com/tools" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent"> | |||
| <!-- Share events preference --> | |||
| <TextView | |||
| android:id="@+id/share_events_title" | |||
| android:id="@+id/allow_self_signed" | |||
| android:layout_width="0dp" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginStart="32dp" | |||
| android:layout_marginTop="32dp" | |||
| android:layout_marginEnd="16dp" | |||
| android:fontFamily="sans-serif" | |||
| android:text="Share events" | |||
| android:text="Allow self signed certificates" | |||
| android:textColor="@android:color/primary_text_light" | |||
| android:textSize="14sp" | |||
| app:layout_constraintEnd_toStartOf="@+id/share_events_switch" | |||
| app:layout_constraintEnd_toStartOf="@+id/self_signed_switch" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" /> | |||
| @@ -28,20 +25,20 @@ | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginStart="32dp" | |||
| android:layout_marginEnd="16dp" | |||
| android:text="This will generate a sync server on your instance" | |||
| android:text="Server certificate must not be from a trusted CA (not recomended)" | |||
| android:textSize="12sp" | |||
| app:layout_constraintEnd_toStartOf="@+id/share_events_switch" | |||
| app:layout_constraintEnd_toStartOf="@+id/self_signed_switch" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toBottomOf="@+id/share_events_title" /> | |||
| app:layout_constraintTop_toBottomOf="@+id/allow_self_signed" /> | |||
| <Switch | |||
| android:id="@+id/share_events_switch" | |||
| android:id="@+id/self_signed_switch" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="0dp" | |||
| android:layout_marginEnd="32dp" | |||
| app:layout_constraintBottom_toBottomOf="@+id/share_events_desc" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintTop_toTopOf="@+id/share_events_title" /> | |||
| app:layout_constraintTop_toTopOf="@+id/allow_self_signed" /> | |||
| <TextView | |||
| android:id="@+id/push_title" | |||
| @@ -147,18 +144,4 @@ | |||
| app:layout_constraintEnd_toStartOf="@+id/cache_switch" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toBottomOf="@+id/cache_title" /> | |||
| <android.support.design.widget.FloatingActionButton | |||
| android:id="@+id/sync_options_fab" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginEnd="16dp" | |||
| android:layout_marginBottom="16dp" | |||
| android:clickable="true" | |||
| android:focusable="true" | |||
| app:layout_constraintBottom_toBottomOf="parent" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:srcCompat="@drawable/ic_check" /> | |||
| </android.support.constraint.ConstraintLayout> | |||
| </androidx.constraintlayout.widget.ConstraintLayout> | |||
| @@ -1,5 +1,5 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | |||
| <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | |||
| xmlns:app="http://schemas.android.com/apk/res-auto" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| @@ -9,7 +9,7 @@ | |||
| android:id="@+id/material_preference_src" | |||
| android:layout_width="24dp" | |||
| android:layout_height="24dp" | |||
| android:layout_marginStart="24dp" | |||
| android:layout_marginStart="16dp" | |||
| android:layout_marginTop="8dp" | |||
| android:layout_marginBottom="8dp" | |||
| android:tint="@color/colorPrimaryDark" | |||
| @@ -22,9 +22,9 @@ | |||
| android:id="@+id/material_preference_title" | |||
| android:layout_width="0dp" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginStart="24dp" | |||
| android:layout_marginStart="16dp" | |||
| android:layout_marginTop="16dp" | |||
| android:layout_marginEnd="24dp" | |||
| android:layout_marginEnd="16dp" | |||
| android:text="Language" | |||
| android:textAppearance="@style/Text.Title" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| @@ -35,8 +35,8 @@ | |||
| android:id="@+id/material_preference_subtitle" | |||
| android:layout_width="0dp" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginStart="24dp" | |||
| android:layout_marginEnd="24dp" | |||
| android:layout_marginStart="16dp" | |||
| android:layout_marginEnd="16dp" | |||
| android:layout_marginBottom="16dp" | |||
| android:text="German" | |||
| android:textAppearance="@style/Text.SubTitle" | |||
| @@ -45,4 +45,4 @@ | |||
| app:layout_constraintStart_toEndOf="@+id/material_preference_src" | |||
| app:layout_constraintTop_toBottomOf="@+id/material_preference_title" /> | |||
| </android.support.constraint.ConstraintLayout> | |||
| </androidx.constraintlayout.widget.ConstraintLayout> | |||
| @@ -0,0 +1,45 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <androidx.appcompat.widget.LinearLayoutCompat | |||
| xmlns:android="http://schemas.android.com/apk/res/android" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:orientation="vertical" | |||
| android:padding="16dp"> | |||
| <LinearLayout | |||
| android:orientation="horizontal" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content"> | |||
| <TextView | |||
| android:id="@+id/title" | |||
| android:textAppearance="@style/Text.Title" | |||
| android:layout_weight="1" | |||
| android:layout_gravity="center_vertical" | |||
| android:layout_width="0dp" | |||
| android:layout_height="wrap_content" /> | |||
| <ImageView | |||
| android:id="@+id/stateView" | |||
| android:src="@drawable/ic_info_outline" | |||
| android:tint="@color/status_amber" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:contentDescription="Upload state" /> | |||
| <ProgressBar | |||
| android:id="@+id/progressBar" | |||
| android:visibility="gone" | |||
| android:layout_width="24dp" | |||
| android:layout_height="24dp" /> | |||
| </LinearLayout> | |||
| <TextView | |||
| android:id="@+id/error" | |||
| android:textAppearance="@style/Text.SubTitle" | |||
| android:textColor="@color/status_red" | |||
| android:layout_marginEnd="24dp" | |||
| android:visibility="gone" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" /> | |||
| </androidx.appcompat.widget.LinearLayoutCompat> | |||
| @@ -0,0 +1,156 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <com.google.android.material.card.MaterialCardView | |||
| xmlns:android="http://schemas.android.com/apk/res/android" | |||
| xmlns:app="http://schemas.android.com/apk/res-auto" | |||
| xmlns:tools="http://schemas.android.com/tools" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:clickable="true" | |||
| android:focusable="true" | |||
| android:layout_margin="8dp" | |||
| android:animateLayoutChanges="true"> | |||
| <androidx.appcompat.widget.LinearLayoutCompat | |||
| android:orientation="vertical" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:clickable="true" | |||
| android:focusable="true"> | |||
| <androidx.constraintlayout.widget.ConstraintLayout | |||
| android:id="@+id/collapsedContent" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content"> | |||
| <TextView | |||
| android:id="@+id/orgName" | |||
| android:layout_width="0dp" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginStart="16dp" | |||
| android:layout_marginTop="16dp" | |||
| android:layout_marginEnd="8dp" | |||
| android:layout_marginBottom="16dp" | |||
| android:textAppearance="@style/Text.Title" | |||
| app:layout_constraintBottom_toBottomOf="parent" | |||
| app:layout_constraintEnd_toStartOf="@+id/date" | |||
| app:layout_constraintStart_toEndOf="@+id/syncStatus" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| tools:text="Organisation A" /> | |||
| <TextView | |||
| android:id="@+id/date" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginTop="16dp" | |||
| android:layout_marginEnd="8dp" | |||
| android:layout_marginBottom="16dp" | |||
| android:textAppearance="@style/Text.SubTitle" | |||
| app:layout_constraintBottom_toBottomOf="parent" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| tools:text="20.3.2019" /> | |||
| <ImageView | |||
| android:id="@+id/syncStatus" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginStart="16dp" | |||
| android:layout_marginTop="16dp" | |||
| android:layout_marginBottom="16dp" | |||
| android:tint="@color/status_red" | |||
| app:layout_constraintBottom_toBottomOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| app:srcCompat="@drawable/ic_error_outline" /> | |||
| </androidx.constraintlayout.widget.ConstraintLayout> | |||
| <androidx.constraintlayout.widget.ConstraintLayout | |||
| android:id="@+id/expandedContent" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:visibility="visible"> | |||
| <lu.circl.mispbump.custom_views.MaterialPreferenceText | |||
| android:id="@+id/email" | |||
| android:layout_width="0dp" | |||
| android:layout_height="wrap_content" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| app:pref_icon="@drawable/ic_email_black_24dp" | |||
| app:subText="email@email.de" | |||
| app:text="Email" /> | |||
| <lu.circl.mispbump.custom_views.MaterialPreferenceText | |||
| android:id="@+id/password" | |||
| android:layout_width="0dp" | |||
| android:layout_height="wrap_content" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintHorizontal_bias="0.0" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toBottomOf="@+id/email" | |||
| app:pref_icon="@drawable/ic_key" | |||
| app:subText="000000000000" | |||
| app:text="Password" /> | |||
| <ImageButton | |||
| android:id="@+id/retryButton" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginEnd="8dp" | |||
| android:background="?attr/selectableItemBackground" | |||
| android:padding="8dp" | |||
| app:layout_constraintBottom_toBottomOf="parent" | |||
| app:layout_constraintEnd_toStartOf="@+id/deleteButton" | |||
| app:layout_constraintTop_toBottomOf="@+id/password" | |||
| app:srcCompat="@drawable/ic_autorenew" /> | |||
| <ImageButton | |||
| android:id="@+id/deleteButton" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginEnd="8dp" | |||
| android:background="?attr/selectableItemBackground" | |||
| android:padding="8dp" | |||
| app:layout_constraintBottom_toBottomOf="parent" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintTop_toBottomOf="@+id/password" | |||
| app:srcCompat="@drawable/ic_delete_forever" /> | |||
| </androidx.constraintlayout.widget.ConstraintLayout> | |||
| <!--<LinearLayout--> | |||
| <!--android:orientation="horizontal"--> | |||
| <!--android:layout_width="match_parent"--> | |||
| <!--android:layout_height="wrap_content"--> | |||
| <!--android:padding="8dp">--> | |||
| <!--<View--> | |||
| <!--android:layout_width="0dp"--> | |||
| <!--android:layout_height="0dp"--> | |||
| <!--android:layout_weight="1" />--> | |||
| <!--<ImageButton--> | |||
| <!--android:id="@+id/retryButton"--> | |||
| <!--android:layout_width="wrap_content"--> | |||
| <!--android:layout_height="wrap_content"--> | |||
| <!--android:layout_marginEnd="8dp"--> | |||
| <!--android:background="?attr/selectableItemBackground"--> | |||
| <!--android:padding="8dp"--> | |||
| <!--app:srcCompat="@drawable/ic_autorenew" />--> | |||
| <!--<ImageButton--> | |||
| <!--android:id="@+id/deleteButton"--> | |||
| <!--android:layout_width="wrap_content"--> | |||
| <!--android:layout_height="wrap_content"--> | |||
| <!--android:background="?attr/selectableItemBackground"--> | |||
| <!--android:padding="8dp"--> | |||
| <!--app:srcCompat="@drawable/ic_delete_forever" />--> | |||
| <!--</LinearLayout>--> | |||
| </androidx.appcompat.widget.LinearLayoutCompat> | |||
| </com.google.android.material.card.MaterialCardView> | |||
| @@ -1,81 +0,0 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <android.support.design.card.MaterialCardView | |||
| xmlns:android="http://schemas.android.com/apk/res/android" | |||
| xmlns:app="http://schemas.android.com/apk/res-auto" | |||
| xmlns:tools="http://schemas.android.com/tools" | |||
| android:id="@+id/rootLayout" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginTop="16dp" | |||
| android:layout_marginStart="16dp" | |||
| android:layout_marginEnd="16dp" | |||
| app:cardElevation="2dp"> | |||
| <android.support.constraint.ConstraintLayout | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent"> | |||
| <ImageView | |||
| android:id="@+id/syncStatus" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="0dp" | |||
| android:background="@color/status_red" | |||
| android:paddingStart="4dp" | |||
| android:paddingEnd="4dp" | |||
| app:layout_constraintBottom_toBottomOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| app:srcCompat="@drawable/ic_error_outline" /> | |||
| <TextView | |||
| android:id="@+id/orgName" | |||
| android:layout_width="0dp" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginStart="16dp" | |||
| android:layout_marginTop="16dp" | |||
| android:layout_marginEnd="8dp" | |||
| android:text="Organisation A" | |||
| android:textAppearance="@style/Text.Title" | |||
| app:layout_constraintEnd_toStartOf="@+id/retryButton" | |||
| app:layout_constraintStart_toEndOf="@+id/syncStatus" | |||
| app:layout_constraintTop_toTopOf="parent" /> | |||
| <TextView | |||
| android:id="@+id/date" | |||
| android:layout_width="0dp" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginStart="16dp" | |||
| android:layout_marginEnd="8dp" | |||
| android:layout_marginBottom="16dp" | |||
| android:text="20.6.2019" | |||
| android:textAppearance="@style/Text.SubTitle" | |||
| app:layout_constraintBottom_toBottomOf="parent" | |||
| app:layout_constraintEnd_toStartOf="@+id/retryButton" | |||
| app:layout_constraintStart_toEndOf="@+id/syncStatus" | |||
| app:layout_constraintTop_toBottomOf="@+id/orgName" /> | |||
| <ImageButton | |||
| android:id="@+id/retryButton" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginEnd="8dp" | |||
| android:background="?attr/selectableItemBackground" | |||
| android:padding="8dp" | |||
| app:layout_constraintBottom_toBottomOf="@+id/syncStatus" | |||
| app:layout_constraintEnd_toStartOf="@+id/deleteButton" | |||
| app:layout_constraintTop_toTopOf="@+id/syncStatus" | |||
| app:srcCompat="@drawable/ic_autorenew" /> | |||
| <ImageButton | |||
| android:id="@+id/deleteButton" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:background="?attr/selectableItemBackground" | |||
| android:padding="8dp" | |||
| android:layout_marginEnd="8dp" | |||
| app:layout_constraintBottom_toBottomOf="@+id/syncStatus" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintTop_toTopOf="@+id/syncStatus" | |||
| app:srcCompat="@drawable/ic_delete_forever" /> | |||
| </android.support.constraint.ConstraintLayout> | |||
| </android.support.design.card.MaterialCardView> | |||
| @@ -1,5 +1,5 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <android.support.constraint.ConstraintLayout | |||
| <androidx.constraintlayout.widget.ConstraintLayout | |||
| xmlns:android="http://schemas.android.com/apk/res/android" | |||
| xmlns:app="http://schemas.android.com/apk/res-auto" | |||
| xmlns:tools="http://schemas.android.com/tools" | |||
| @@ -40,4 +40,4 @@ | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toBottomOf="@id/viewholder_user_description" /> | |||
| </android.support.constraint.ConstraintLayout> | |||
| </androidx.constraintlayout.widget.ConstraintLayout> | |||
| @@ -4,7 +4,7 @@ | |||
| <item | |||
| android:id="@+id/menu_login_help" | |||
| android:icon="@drawable/ic_help_outline_black_24dp" | |||
| android:icon="@drawable/ic_help_outline" | |||
| android:title="@string/menu_login_help_label" | |||
| app:showAsAction="ifRoom"/> | |||
| @@ -1,14 +1,13 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <resources> | |||
| <string name="app_name">MispBump</string> | |||
| <string name="download_misp_infos">MISP Informationen laden</string> | |||
| <string name="login">Anmelden</string> | |||
| <string name="logout">Abmelden</string> | |||
| <string name="menu_login_help_label">Hilfe</string> | |||
| <string name="misp_automation_hint">MISP Automatisierungs-Schlüssel</string> | |||
| <string name="no_information">Keine Informationen</string> | |||
| <string name="save_automation_key_hint">Automatisierungs-Schlüssel speichern</string> | |||
| <string name="login_help_text">Das ist der Anmelde Informations Text.</string> | |||
| <string name="login_help_text"><b>MISP Server URL</b>\nDie URL unter der Ihre MISP Instanz erreichbar ist.\n\n<b>MISP Automatisierungs Schlüssel</b>\nZu finden unter ...</string> | |||
| <string name="qr_code">QR code</string> | |||
| <string name="sync">Synchronisation</string> | |||
| <string name="no_syncs_hint">Sie haben noch keine MISP Instanzen verknüpft.</string> | |||
| @@ -5,4 +5,8 @@ | |||
| <attr name="text"/> | |||
| <attr name="subText"/> | |||
| </declare-styleable> | |||
| <declare-styleable name="UploadAction"> | |||
| <attr name="title" format="string"/> | |||
| </declare-styleable> | |||
| </resources> | |||
| @@ -2,6 +2,7 @@ | |||
| <resources> | |||
| <color name="colorPrimary">#047EB4</color> | |||
| <color name="colorPrimaryDark">#023850</color> | |||
| <!--<color name="colorPrimaryDark">#047EB4</color>--> | |||
| <color name="colorAccent">#12B3FA</color> | |||
| <color name="colorAccent_50">#8012B3FA</color> | |||
| @@ -11,5 +12,6 @@ | |||
| <color name="white">#FFFFFF</color> | |||
| <color name="white_50">#80FFFFFF</color> | |||
| <color name="status_green">#4CAF50</color> | |||
| <color name="status_amber">#FB8C00</color> | |||
| <color name="status_red">#E53935</color> | |||
| </resources> | |||
| @@ -4,12 +4,11 @@ | |||
| <string name="logout">Log out</string> | |||
| <string name="misp_server_url_hint" translatable="false">MISP Server URL</string> | |||
| <string name="misp_automation_hint">MISP Automation Key</string> | |||
| <string name="download_misp_infos">Download MISP Infos</string> | |||
| <string name="no_information">No Information</string> | |||
| <string name="save_automation_key_hint">Save Automation Key</string> | |||
| <string name="home" translatable="false">Home</string> | |||
| <string name="menu_login_help_label">Help</string> | |||
| <string name="login_help_text">This is the login info text.</string> | |||
| <string name="login_help_text"><b>MISP Server URL</b>\nPublic MISP URL\n\n<b>MISP Automation key</b>\nZu finden unter ...</string> | |||
| <string name="ok" translatable="false">Okay</string> | |||
| <string name="qr_code">QR code</string> | |||
| <string name="sync">Synchronization</string> | |||
| @@ -1,11 +1,14 @@ | |||
| <resources> | |||
| <!-- Theme.MaterialComponents.Light.NoActionBar || Theme.AppCompat.Light.NoActionBar --> | |||
| <style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar"> | |||
| <item name="colorPrimary">@color/colorPrimary</item> | |||
| <item name="colorPrimaryDark">@color/colorPrimaryDark</item> | |||
| <item name="colorAccent">@color/colorAccent</item> | |||
| </style> | |||
| <style name="AppTheme.Translucent" parent="AppTheme"> | |||
| <style name="AppTheme.Translucent"> | |||
| <item name="windowActionBarOverlay">true</item> | |||
| <item name="android:windowTranslucentStatus">true</item> | |||
| </style> | |||
| @@ -6,6 +6,8 @@ | |||
| # http://www.gradle.org/docs/current/userguide/build_environment.html | |||
| # Specifies the JVM arguments used for the daemon process. | |||
| # The setting is particularly useful for tweaking memory settings. | |||
| android.useAndroidX=true | |||
| android.enableJetifier=true | |||
| org.gradle.jvmargs=-Xmx1536m | |||
| # When configured, Gradle will run in incubating parallel mode. | |||
| # This option should only be used with decoupled projects. More details, visit | |||
| @@ -1,25 +0,0 @@ | |||
| # Information in encrypted QR code (on MISP A) | |||
| ## Organisation | |||
| + Identifier (Organisation B on MISP A) | |||
| + UUID (of organisation B) | |||
| + description | |||
| + Nationality | |||
| + Sector | |||
| + type (freetext) | |||
| + (contacts?) | |||
| ## SyncUser | |||
| + email (orgb.syncuser@mispa.test) | |||
| + password (abcdefghijklmnop) (16 chars but depends on settings) | |||
| ## SyncServer | |||
| + base url | |||
| + instance name | |||
| + authkey from syncUser | |||
| ## Steps | |||
| 1. each instance generates a foreign organisation | |||
| 2. each instance generates a syncuser | |||
| 3. each instance generates a sync server | |||