2019-07-02 23:40:42 +02:00
|
|
|
package lu.circl.mispbump.activities;
|
|
|
|
|
|
|
|
import android.content.Intent;
|
|
|
|
import android.content.res.ColorStateList;
|
|
|
|
import android.graphics.Bitmap;
|
|
|
|
import android.graphics.drawable.Drawable;
|
|
|
|
import android.os.Bundle;
|
|
|
|
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;
|
2019-07-14 19:17:32 +02:00
|
|
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
2019-07-02 23:40:42 +02:00
|
|
|
import androidx.fragment.app.FragmentManager;
|
|
|
|
import androidx.fragment.app.FragmentTransaction;
|
|
|
|
|
|
|
|
import com.google.android.material.snackbar.Snackbar;
|
|
|
|
import com.google.gson.Gson;
|
|
|
|
import com.google.gson.JsonSyntaxException;
|
|
|
|
|
|
|
|
import java.security.NoSuchAlgorithmException;
|
|
|
|
import java.security.spec.InvalidKeySpecException;
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
import lu.circl.mispbump.R;
|
|
|
|
import lu.circl.mispbump.auxiliary.DialogManager;
|
|
|
|
import lu.circl.mispbump.auxiliary.PreferenceManager;
|
|
|
|
import lu.circl.mispbump.auxiliary.QrCodeGenerator;
|
|
|
|
import lu.circl.mispbump.auxiliary.RandomString;
|
|
|
|
import lu.circl.mispbump.fragments.CameraFragment;
|
|
|
|
import lu.circl.mispbump.models.SyncInformation;
|
|
|
|
import lu.circl.mispbump.models.UploadInformation;
|
|
|
|
import lu.circl.mispbump.security.DiffieHellman;
|
|
|
|
|
|
|
|
public class ExchangeActivity extends AppCompatActivity {
|
|
|
|
|
|
|
|
private PreferenceManager preferenceManager;
|
|
|
|
private QrCodeGenerator qrCodeGenerator;
|
|
|
|
private DiffieHellman diffieHellman;
|
|
|
|
private UploadInformation uploadInformation;
|
|
|
|
|
|
|
|
private CameraFragment cameraFragment;
|
|
|
|
|
2019-07-14 19:17:32 +02:00
|
|
|
private ConstraintLayout rootLayout;
|
2019-07-14 18:18:54 +02:00
|
|
|
private View qrFrame, scanFeedbackView, continueHintView, fragmentContainer;
|
2019-07-14 19:17:32 +02:00
|
|
|
private TextView scanFeedbackText, qrContentInfo;
|
2019-07-02 23:40:42 +02:00
|
|
|
private ImageView qrCode;
|
|
|
|
private ImageButton prevButton, nextButton;
|
|
|
|
|
|
|
|
private Bitmap publicKeyQr, dataQr;
|
|
|
|
|
|
|
|
private SyncState currentSyncState;
|
2019-07-04 12:51:37 +02:00
|
|
|
private ReadQrStatus currentReadQrStatus;
|
2019-07-02 23:40:42 +02:00
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
|
|
super.onCreate(savedInstanceState);
|
2019-07-14 19:17:32 +02:00
|
|
|
setContentView(R.layout.activity_exchange);
|
2019-07-02 23:40:42 +02:00
|
|
|
|
|
|
|
preferenceManager = PreferenceManager.getInstance(ExchangeActivity.this);
|
|
|
|
qrCodeGenerator = new QrCodeGenerator(ExchangeActivity.this);
|
|
|
|
diffieHellman = DiffieHellman.getInstance();
|
|
|
|
|
|
|
|
initViews();
|
|
|
|
initCamera();
|
|
|
|
|
|
|
|
uploadInformation = new UploadInformation();
|
|
|
|
publicKeyQr = generatePublicKeyBitmap();
|
|
|
|
dataQr = generateLocalSyncInfoBitmap();
|
|
|
|
|
|
|
|
setSyncState(SyncState.KEY_EXCHANGE);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void initViews() {
|
|
|
|
rootLayout = findViewById(R.id.rootLayout);
|
2019-07-14 18:18:54 +02:00
|
|
|
fragmentContainer = findViewById(R.id.fragmentContainer);
|
2019-07-02 23:40:42 +02:00
|
|
|
|
|
|
|
qrFrame = findViewById(R.id.qrFrame);
|
|
|
|
qrCode = findViewById(R.id.qrCode);
|
2019-07-14 18:18:54 +02:00
|
|
|
|
|
|
|
scanFeedbackView = findViewById(R.id.scanFeedbackView);
|
|
|
|
scanFeedbackText = findViewById(R.id.scanFeedbackText);
|
|
|
|
continueHintView = findViewById(R.id.continueHint);
|
2019-07-14 19:17:32 +02:00
|
|
|
qrContentInfo = findViewById(R.id.qrContentInfo);
|
2019-07-02 23:40:42 +02:00
|
|
|
|
|
|
|
prevButton = findViewById(R.id.prevButton);
|
|
|
|
prevButton.setOnClickListener(onPrevClicked());
|
|
|
|
|
|
|
|
nextButton = findViewById(R.id.nextButton);
|
|
|
|
nextButton.setOnClickListener(onNextClicked());
|
|
|
|
}
|
|
|
|
|
|
|
|
private void initCamera() {
|
|
|
|
FragmentManager fragmentManager = getSupportFragmentManager();
|
|
|
|
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
|
|
|
|
|
|
|
|
cameraFragment = new CameraFragment();
|
|
|
|
cameraFragment.setOnQrAvailableListener(onQrScanned());
|
|
|
|
|
|
|
|
String fragmentTag = cameraFragment.getClass().getSimpleName();
|
|
|
|
fragmentTransaction.add(R.id.fragmentContainer, cameraFragment, fragmentTag);
|
|
|
|
fragmentTransaction.commit();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Bitmap generatePublicKeyBitmap() {
|
|
|
|
return qrCodeGenerator.generateQrCode(DiffieHellman.publicKeyToString(diffieHellman.getPublicKey()));
|
|
|
|
}
|
|
|
|
|
|
|
|
private Bitmap generateLocalSyncInfoBitmap() {
|
|
|
|
uploadInformation.setLocal(generateLocalSyncInfo());
|
|
|
|
return qrCodeGenerator.generateQrCode(new Gson().toJson(uploadInformation.getLocal()));
|
|
|
|
}
|
|
|
|
|
|
|
|
private SyncInformation generateLocalSyncInfo() {
|
|
|
|
SyncInformation syncInformation = new SyncInformation();
|
|
|
|
syncInformation.organisation = preferenceManager.getUserOrganisation().toSyncOrganisation();
|
|
|
|
syncInformation.syncUserAuthkey = new RandomString(40).nextString();
|
|
|
|
syncInformation.baseUrl = preferenceManager.getServerUrl();
|
|
|
|
syncInformation.syncUserPassword = new RandomString(16).nextString();
|
|
|
|
syncInformation.syncUserEmail = preferenceManager.getUserInfo().email;
|
|
|
|
return syncInformation;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void showQrCode(final Bitmap bitmap) {
|
|
|
|
runOnUiThread(new Runnable() {
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
qrCode.setImageBitmap(bitmap);
|
2019-07-14 19:17:32 +02:00
|
|
|
qrFrame.setVisibility(View.VISIBLE);
|
2019-07-02 23:40:42 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private void setSyncState(SyncState state) {
|
|
|
|
currentSyncState = state;
|
|
|
|
|
|
|
|
runOnUiThread(new Runnable() {
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
switch (currentSyncState) {
|
|
|
|
case KEY_EXCHANGE:
|
|
|
|
prevButton.setImageDrawable(getDrawable(R.drawable.ic_close));
|
|
|
|
prevButton.setVisibility(View.VISIBLE);
|
|
|
|
nextButton.setVisibility(View.GONE);
|
|
|
|
|
|
|
|
setCameraPreviewEnabled(true);
|
|
|
|
showQrCode(publicKeyQr);
|
2019-07-14 19:17:32 +02:00
|
|
|
|
|
|
|
setReadQrStatus(ReadQrStatus.PENDING);
|
|
|
|
scanFeedbackText.setText(R.string.scan_qr_hint);
|
|
|
|
qrContentInfo.setText(R.string.public_key);
|
2019-07-02 23:40:42 +02:00
|
|
|
break;
|
|
|
|
case KEY_EXCHANGE_DONE:
|
|
|
|
prevButton.setImageDrawable(getDrawable(R.drawable.ic_close));
|
|
|
|
prevButton.setVisibility(View.VISIBLE);
|
|
|
|
nextButton.setImageDrawable(getDrawable(R.drawable.ic_arrow_forward));
|
|
|
|
nextButton.setVisibility(View.VISIBLE);
|
|
|
|
|
|
|
|
setCameraPreviewEnabled(false);
|
|
|
|
showQrCode(publicKeyQr);
|
2019-07-14 19:17:32 +02:00
|
|
|
|
|
|
|
setReadQrStatus(ReadQrStatus.SUCCESS);
|
|
|
|
scanFeedbackText.setText(R.string.public_key_received_hint);
|
|
|
|
qrContentInfo.setText(R.string.public_key);
|
2019-07-02 23:40:42 +02:00
|
|
|
break;
|
|
|
|
case DATA_EXCHANGE:
|
|
|
|
prevButton.setImageDrawable(getDrawable(R.drawable.ic_arrow_back));
|
|
|
|
prevButton.setVisibility(View.VISIBLE);
|
|
|
|
nextButton.setVisibility(View.GONE);
|
|
|
|
|
|
|
|
setCameraPreviewEnabled(true);
|
|
|
|
showQrCode(dataQr);
|
2019-07-14 19:17:32 +02:00
|
|
|
|
|
|
|
setReadQrStatus(ReadQrStatus.PENDING);
|
|
|
|
scanFeedbackText.setText(R.string.scan_qr_hint);
|
|
|
|
qrContentInfo.setText(R.string.sync_information);
|
2019-07-02 23:40:42 +02:00
|
|
|
break;
|
|
|
|
case DATA_EXCHANGE_DONE:
|
|
|
|
prevButton.setImageDrawable(getDrawable(R.drawable.ic_arrow_back));
|
|
|
|
prevButton.setVisibility(View.VISIBLE);
|
|
|
|
nextButton.setImageDrawable(getDrawable(R.drawable.ic_check));
|
|
|
|
nextButton.setVisibility(View.VISIBLE);
|
|
|
|
|
|
|
|
setCameraPreviewEnabled(false);
|
|
|
|
showQrCode(dataQr);
|
2019-07-14 19:17:32 +02:00
|
|
|
|
|
|
|
setReadQrStatus(ReadQrStatus.SUCCESS);
|
|
|
|
scanFeedbackText.setText(R.string.sync_info_received_hint);
|
|
|
|
qrContentInfo.setText(R.string.public_key);
|
2019-07-02 23:40:42 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private void setReadQrStatus(ReadQrStatus status) {
|
2019-07-04 12:51:37 +02:00
|
|
|
currentReadQrStatus = status;
|
2019-07-02 23:40:42 +02:00
|
|
|
|
|
|
|
final Drawable drawable;
|
|
|
|
final int color;
|
|
|
|
|
|
|
|
switch (status) {
|
|
|
|
case PENDING:
|
|
|
|
drawable = getDrawable(R.drawable.ic_info_outline);
|
|
|
|
color = getColor(R.color.status_amber);
|
|
|
|
break;
|
|
|
|
case SUCCESS:
|
|
|
|
drawable = getDrawable(R.drawable.ic_check_outline);
|
|
|
|
color = getColor(R.color.status_green);
|
|
|
|
break;
|
|
|
|
case FAILURE:
|
|
|
|
drawable = getDrawable(R.drawable.ic_error_outline);
|
|
|
|
color = getColor(R.color.status_red);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
drawable = getDrawable(R.drawable.ic_info_outline);
|
2019-07-14 18:18:54 +02:00
|
|
|
color = getColor(R.color.status_amber);
|
2019-07-02 23:40:42 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2019-07-14 19:17:32 +02:00
|
|
|
scanFeedbackText.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null);
|
|
|
|
scanFeedbackText.setCompoundDrawableTintList(ColorStateList.valueOf(color));
|
2019-07-14 18:18:54 +02:00
|
|
|
|
2019-07-14 19:17:32 +02:00
|
|
|
if (currentReadQrStatus == ReadQrStatus.SUCCESS) {
|
|
|
|
continueHintView.setVisibility(View.VISIBLE);
|
|
|
|
scanFeedbackView.setBackgroundTintList(ColorStateList.valueOf(getColor(R.color.white)));
|
|
|
|
qrFrame.setBackgroundTintList(ColorStateList.valueOf(getColor(R.color.white)));
|
2019-07-14 18:18:54 +02:00
|
|
|
|
2019-07-14 19:17:32 +02:00
|
|
|
fragmentContainer.animate().alpha(0).setDuration(250).start();
|
|
|
|
} else {
|
|
|
|
continueHintView.setVisibility(View.GONE);
|
|
|
|
scanFeedbackView.setBackgroundTintList(ColorStateList.valueOf(getColor(R.color.white_80)));
|
|
|
|
qrFrame.setBackgroundTintList(ColorStateList.valueOf(getColor(R.color.white_80)));
|
2019-07-14 18:18:54 +02:00
|
|
|
|
2019-07-14 19:17:32 +02:00
|
|
|
fragmentContainer.animate().alpha(1).setDuration(250).start();
|
|
|
|
}
|
2019-07-02 23:40:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private void setCameraPreviewEnabled(boolean enabled) {
|
|
|
|
View view = findViewById(R.id.fragmentContainer);
|
|
|
|
|
|
|
|
if (enabled) {
|
|
|
|
Log.d("DEBUG", "cameraPreview enabled");
|
|
|
|
view.animate()
|
|
|
|
.alpha(1f)
|
|
|
|
.setDuration(250)
|
|
|
|
.start();
|
|
|
|
cameraFragment.setReadQrEnabled(true);
|
|
|
|
} else {
|
|
|
|
Log.d("DEBUG", "cameraPreview disabled");
|
|
|
|
view.animate()
|
|
|
|
.alpha(0f)
|
|
|
|
.setDuration(250)
|
|
|
|
.start();
|
|
|
|
cameraFragment.setReadQrEnabled(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private CameraFragment.QrScanCallback onQrScanned() {
|
|
|
|
return new CameraFragment.QrScanCallback() {
|
|
|
|
@Override
|
|
|
|
public void qrScanResult(String qrData) {
|
|
|
|
cameraFragment.setReadQrEnabled(false);
|
|
|
|
|
|
|
|
switch (currentSyncState) {
|
|
|
|
case KEY_EXCHANGE:
|
|
|
|
try {
|
|
|
|
diffieHellman.setForeignPublicKey(DiffieHellman.publicKeyFromString(qrData));
|
|
|
|
setSyncState(SyncState.KEY_EXCHANGE_DONE);
|
|
|
|
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
|
2019-07-04 12:51:37 +02:00
|
|
|
if (currentReadQrStatus == ReadQrStatus.PENDING) {
|
|
|
|
setReadQrStatus(ReadQrStatus.FAILURE);
|
|
|
|
Snackbar.make(rootLayout, "Public key not parsable", Snackbar.LENGTH_LONG).show();
|
|
|
|
}
|
|
|
|
|
2019-07-02 23:40:42 +02:00
|
|
|
cameraFragment.setReadQrEnabled(true);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case DATA_EXCHANGE:
|
|
|
|
try {
|
|
|
|
final SyncInformation remoteSyncInfo = new Gson().fromJson(diffieHellman.decrypt(qrData), SyncInformation.class);
|
2019-07-03 14:50:43 +02:00
|
|
|
|
|
|
|
final List<UploadInformation> uploadInformationList = preferenceManager.getUploadInformationList();
|
2019-07-02 23:40:42 +02:00
|
|
|
|
|
|
|
for (final UploadInformation ui : uploadInformationList) {
|
|
|
|
if (ui.getRemote().organisation.uuid.equals(remoteSyncInfo.organisation.uuid)) {
|
|
|
|
DialogManager.syncAlreadyExistsDialog(ui.getRemote(), remoteSyncInfo, ExchangeActivity.this, new DialogManager.IDialogFeedback() {
|
|
|
|
@Override
|
|
|
|
public void positive() {
|
|
|
|
// update remote info only
|
|
|
|
uploadInformation.setUuid(ui.getUuid());
|
|
|
|
uploadInformation.setDate();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void negative() {
|
|
|
|
// replace credentials too
|
2019-07-03 14:50:43 +02:00
|
|
|
uploadInformationList.remove(ui);
|
|
|
|
preferenceManager.setUploadInformationList(uploadInformationList);
|
2019-07-02 23:40:42 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
uploadInformation.setRemote(remoteSyncInfo);
|
|
|
|
preferenceManager.addUploadInformation(uploadInformation);
|
|
|
|
setSyncState(SyncState.DATA_EXCHANGE_DONE);
|
|
|
|
} catch (JsonSyntaxException e) {
|
2019-07-04 12:51:37 +02:00
|
|
|
if (currentReadQrStatus == ReadQrStatus.PENDING) {
|
|
|
|
setReadQrStatus(ReadQrStatus.FAILURE);
|
|
|
|
Snackbar.make(rootLayout, "Sync information not parsable", Snackbar.LENGTH_LONG).show();
|
|
|
|
}
|
|
|
|
|
2019-07-02 23:40:42 +02:00
|
|
|
cameraFragment.setReadQrEnabled(true);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
private View.OnClickListener onPrevClicked() {
|
|
|
|
return new View.OnClickListener() {
|
|
|
|
@Override
|
|
|
|
public void onClick(View v) {
|
|
|
|
switch (currentSyncState) {
|
|
|
|
case KEY_EXCHANGE:
|
|
|
|
case KEY_EXCHANGE_DONE:
|
|
|
|
// TODO warning that sync will be lost
|
|
|
|
finish();
|
|
|
|
break;
|
|
|
|
case DATA_EXCHANGE:
|
|
|
|
case DATA_EXCHANGE_DONE:
|
|
|
|
setSyncState(SyncState.KEY_EXCHANGE_DONE);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
private View.OnClickListener onNextClicked() {
|
|
|
|
return new View.OnClickListener() {
|
|
|
|
@Override
|
|
|
|
public void onClick(View v) {
|
|
|
|
switch (currentSyncState) {
|
|
|
|
case KEY_EXCHANGE_DONE:
|
|
|
|
setSyncState(SyncState.DATA_EXCHANGE);
|
|
|
|
break;
|
|
|
|
case DATA_EXCHANGE_DONE:
|
|
|
|
uploadInformation.setCurrentSyncStatus(UploadInformation.SyncStatus.PENDING);
|
|
|
|
preferenceManager.addUploadInformation(uploadInformation);
|
|
|
|
Intent i = new Intent(ExchangeActivity.this, UploadInfoActivity.class);
|
|
|
|
i.putExtra(UploadInfoActivity.EXTRA_UPLOAD_INFO_UUID, uploadInformation.getUuid());
|
|
|
|
startActivity(i);
|
|
|
|
finish();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private enum SyncState {
|
|
|
|
KEY_EXCHANGE,
|
|
|
|
KEY_EXCHANGE_DONE,
|
|
|
|
DATA_EXCHANGE,
|
|
|
|
DATA_EXCHANGE_DONE
|
|
|
|
}
|
|
|
|
|
|
|
|
private enum ReadQrStatus {
|
|
|
|
PENDING,
|
|
|
|
SUCCESS,
|
|
|
|
FAILURE
|
|
|
|
}
|
|
|
|
}
|