mirror of https://github.com/MISP/misp-bump
add qr code generator
improve camera performance improve encryption encoding improve syncActivity improve layouts add some localespull/5/head
parent
0505507d1d
commit
642cd133c2
|
@ -15,6 +15,7 @@
|
|||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
|
||||
<activity android:name=".StartUpActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
@ -22,6 +23,7 @@
|
|||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".HomeActivity"
|
||||
android:label="@string/home" />
|
||||
|
@ -30,9 +32,7 @@
|
|||
android:label="@string/login" />
|
||||
<activity
|
||||
android:name=".SyncActivity"
|
||||
android:label="@string/app_name" />
|
||||
<activity
|
||||
android:name=".AESActivity"
|
||||
android:label="@string/app_name"
|
||||
android:parentActivityName=".HomeActivity"/>
|
||||
</application>
|
||||
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
package lu.circl.mispbump;
|
||||
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import lu.circl.mispbump.security.AESSecurity;
|
||||
|
||||
public class AESActivity extends AppCompatActivity {
|
||||
|
||||
private TextView info;
|
||||
private Button button;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_aes);
|
||||
|
||||
// populate Toolbar (Actionbar)
|
||||
Toolbar myToolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(myToolbar);
|
||||
|
||||
ActionBar ab = getSupportActionBar();
|
||||
if (ab != null) {
|
||||
ab.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
info = findViewById(R.id.aes_info);
|
||||
button = findViewById(R.id.aes_button);
|
||||
|
||||
button.setOnClickListener(onButtonClick);
|
||||
}
|
||||
|
||||
private View.OnClickListener onButtonClick = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
AESSecurity sec1 = new AESSecurity();
|
||||
AESSecurity sec2 = new AESSecurity();
|
||||
|
||||
String message = "Geheimer Text von A als auch von B ...";
|
||||
|
||||
String pub1 = sec1.getPublicKey().toString();
|
||||
info.setText("A PK: " + pub1 + "\n");
|
||||
|
||||
String pub2 = sec2.getPublicKey().toString();
|
||||
info.append("B PK: " + pub2 + "\n");
|
||||
|
||||
sec1.setForeignPublicKey(sec2.getPublicKey());
|
||||
sec2.setForeignPublicKey(sec1.getPublicKey());
|
||||
info.append("\n-- public keys wurden ausgetauscht --\n\n");
|
||||
|
||||
String enc1 = sec1.encrypt(message);
|
||||
info.append("A encrypted: " + enc1 + "\n");
|
||||
String enc2 = sec2.encrypt(message);
|
||||
info.append("B encrypted: " + enc2 + "\n\n");
|
||||
|
||||
info.append("A entschlüsselt B's Nachricht: " + sec1.decrypt(enc2) + "\n");
|
||||
info.append("B entschlüsselt A's Nachricht: " + sec2.decrypt(enc1) + "\n");
|
||||
}
|
||||
};
|
||||
}
|
|
@ -57,12 +57,6 @@ public class HomeActivity extends AppCompatActivity {
|
|||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
|
||||
case R.id.aes:
|
||||
Intent i = new Intent(getApplicationContext(), AESActivity.class);
|
||||
startActivity(i);
|
||||
return true;
|
||||
|
||||
case R.id.main_menu_clear_and_logout:
|
||||
clearDeviceAndLogOut();
|
||||
return true;
|
||||
|
|
|
@ -1,62 +1,150 @@
|
|||
package lu.circl.mispbump;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Point;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.EncodeHintType;
|
||||
import com.google.zxing.MultiFormatWriter;
|
||||
import com.google.zxing.WriterException;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.google.zxing.qrcode.QRCodeWriter;
|
||||
import com.journeyapps.barcodescanner.BarcodeEncoder;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
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.restful_client.MispRestClient;
|
||||
import lu.circl.mispbump.restful_client.MispServer;
|
||||
import lu.circl.mispbump.restful_client.MispUser;
|
||||
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.AESSecurity;
|
||||
|
||||
/**
|
||||
* Step 1: Add partner org as local org (uuid must be the same)
|
||||
* Step 2: Add SyncUser to local partner org
|
||||
* Step 3: Add SyncServer with SyncUser's authkey
|
||||
* <p>
|
||||
* What do we need to transmit?
|
||||
* 1. Own organisation details
|
||||
* 2. Authkey of SyncUser
|
||||
* 3. Server url
|
||||
*/
|
||||
public class SyncActivity extends AppCompatActivity {
|
||||
|
||||
private static final String TAG = "SyncActivity";
|
||||
private CameraFragment cameraFragment;
|
||||
|
||||
private MispRestClient restClient;
|
||||
private ImageView qrCodeView;
|
||||
|
||||
private AESSecurity aesSecurity;
|
||||
private MispRestClient restClient;
|
||||
private CameraFragment cameraFragment;
|
||||
private ImageView qrCodeView;
|
||||
private FloatingActionButton continueButton;
|
||||
|
||||
private SyncState currentSyncState = SyncState.publicKeyExchange;
|
||||
|
||||
private enum SyncState {
|
||||
publicKeyExchange,
|
||||
dataExchange
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_sync);
|
||||
|
||||
qrCodeView = findViewById(R.id.qrcode);
|
||||
aesSecurity = AESSecurity.getInstance();
|
||||
Toolbar myToolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(myToolbar);
|
||||
|
||||
showPublicKeyQr();
|
||||
enableCameraFragment();
|
||||
ActionBar ab = getSupportActionBar();
|
||||
if (ab != null) {
|
||||
ab.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
qrCodeView = findViewById(R.id.qrcode);
|
||||
continueButton = findViewById(R.id.continue_fab);
|
||||
continueButton.setOnClickListener(onContinueClicked);
|
||||
continueButton.hide();
|
||||
|
||||
aesSecurity = AESSecurity.getInstance();
|
||||
restClient = new MispRestClient(this);
|
||||
|
||||
enableSyncOptionsFragment();
|
||||
}
|
||||
|
||||
private View.OnClickListener onContinueClicked = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
switch (currentSyncState) {
|
||||
case publicKeyExchange:
|
||||
currentSyncState = SyncState.dataExchange;
|
||||
showInformationQr();
|
||||
continueButton.hide();
|
||||
cameraFragment.setReadQrEnabled(true);
|
||||
break;
|
||||
|
||||
case dataExchange:
|
||||
// TODO upload
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback for the camera fragment.
|
||||
* Delivers the content of a scanned QR code.
|
||||
*/
|
||||
private CameraFragment.QrScanCallback onQrCodeScanned = new CameraFragment.QrScanCallback() {
|
||||
@Override
|
||||
public void qrScanResult(String qrData) {
|
||||
switch (currentSyncState) {
|
||||
case publicKeyExchange:
|
||||
try {
|
||||
aesSecurity.setForeignPublicKey(AESSecurity.publicKeyFromString(qrData));
|
||||
cameraFragment.setReadQrEnabled(false);
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
continueButton.show();
|
||||
}
|
||||
});
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
MakeToast("Something gone wrong while parsing the key (no such algorithm)");
|
||||
} catch (InvalidKeySpecException e) {
|
||||
MakeToast("Something gone wrong while parsing the key (invalid key spec)");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case dataExchange:
|
||||
// disable qr read
|
||||
cameraFragment.setReadQrEnabled(false);
|
||||
|
||||
String data = aesSecurity.decrypt(qrData);
|
||||
SyncInformation info = new Gson().fromJson(data, SyncInformation.class);
|
||||
|
||||
Log.i(TAG, info.organisation.toString());
|
||||
Log.i(TAG, info.user.toString());
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the camera fragment used to scan the QR codes.
|
||||
*/
|
||||
private void enableCameraFragment() {
|
||||
cameraFragment = new CameraFragment();
|
||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
||||
|
@ -65,23 +153,121 @@ public class SyncActivity extends AppCompatActivity {
|
|||
transaction.commit();
|
||||
|
||||
cameraFragment.setReadQrEnabled(true);
|
||||
cameraFragment.setOnQrAvailableListener(resultCallback);
|
||||
cameraFragment.setOnQrAvailableListener(onQrCodeScanned);
|
||||
}
|
||||
|
||||
/**
|
||||
* options for this particular sync
|
||||
*/
|
||||
private void enableSyncOptionsFragment() {
|
||||
SyncOptionsFragment syncOptionsFragment = new SyncOptionsFragment();
|
||||
|
||||
syncOptionsFragment.setOnOptionsReadyCallback(new SyncOptionsFragment.OptionsReadyCallback() {
|
||||
@Override
|
||||
public void ready(boolean share_events, boolean push, boolean pull, boolean caching) {
|
||||
showPublicKeyQr();
|
||||
enableCameraFragment();
|
||||
}
|
||||
});
|
||||
|
||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
||||
FragmentTransaction transaction = fragmentManager.beginTransaction();
|
||||
transaction.replace(R.id.sync_fragment_container, syncOptionsFragment, syncOptionsFragment.getClass().getSimpleName());
|
||||
transaction.commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display public key QR code.
|
||||
*/
|
||||
private void showPublicKeyQr() {
|
||||
Bitmap bm = generateQrCodeFromString(aesSecurity.getPublicKey().toString());
|
||||
QrCodeGenerator qrCodeGenerator = new QrCodeGenerator(this);
|
||||
Bitmap bm = qrCodeGenerator.generateQrCode(AESSecurity.publicKeyToString(aesSecurity.getPublicKey()));
|
||||
qrCodeView.setImageBitmap(bm);
|
||||
qrCodeView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private CameraFragment.QrResultCallback resultCallback = new CameraFragment.QrResultCallback() {
|
||||
@Override
|
||||
public void qrDataResult(String qrData) {
|
||||
// TODO validate data
|
||||
cameraFragment.setReadQrEnabled(false);
|
||||
MakeToast(qrData);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Display sync info QR code.
|
||||
*/
|
||||
private void showInformationQr() {
|
||||
PreferenceManager preferenceManager = PreferenceManager.getInstance(this);
|
||||
QrCodeGenerator qrCodeGenerator = new QrCodeGenerator(this);
|
||||
Gson gson = new Gson();
|
||||
|
||||
// my organisation
|
||||
Organisation org = preferenceManager.getUserOrganisation();
|
||||
User user = preferenceManager.getUserInfo();
|
||||
MispUser mispUser = new MispUser(user);
|
||||
|
||||
Server server = new Server(
|
||||
"SyncServer for " + org.name,
|
||||
preferenceManager.getServerUrl(),
|
||||
new RandomString(40).nextString(),
|
||||
-1
|
||||
);
|
||||
|
||||
MispServer mispServer = new MispServer(server, org, null);
|
||||
|
||||
SyncInformation syncInformation = new SyncInformation(user, org, server);
|
||||
String encrypted = aesSecurity.encrypt(gson.toJson(syncInformation));
|
||||
|
||||
final Bitmap bm = qrCodeGenerator.generateQrCode(encrypted);
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
qrCodeView.setImageBitmap(bm);
|
||||
qrCodeView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addPartnerOrg(Organisation organisation) {
|
||||
restClient.addOrganisation(organisation, new MispRestClient.OrganisationCallback() {
|
||||
@Override
|
||||
public void success(Organisation organisation) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failure(String error) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addSyncUser(User user) {
|
||||
restClient.addUser(user, new MispRestClient.UserCallback() {
|
||||
@Override
|
||||
public void success(User user) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failure(String error) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addServer(MispServer server) {
|
||||
restClient.addServer(server, new MispRestClient.ServerCallback() {
|
||||
@Override
|
||||
public void success(List<MispServer> servers) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void success(MispServer server) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failure(String error) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void MakeToast(final String message) {
|
||||
this.runOnUiThread(new Runnable() {
|
||||
|
@ -92,120 +278,6 @@ public class SyncActivity extends AppCompatActivity {
|
|||
});
|
||||
}
|
||||
|
||||
private Bitmap generateQrCodeFromString(String content) {
|
||||
|
||||
Point displaySize = new Point();
|
||||
this.getWindowManager().getDefaultDisplay().getSize(displaySize);
|
||||
|
||||
int size = displaySize.x;
|
||||
|
||||
if (displaySize.x > displaySize.y) {
|
||||
size = displaySize.y;
|
||||
}
|
||||
|
||||
size = (int)(size * 0.8);
|
||||
|
||||
try {
|
||||
MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
|
||||
|
||||
Map<EncodeHintType, Integer> hints = new HashMap<>();
|
||||
hints.put(EncodeHintType.MARGIN, 0);
|
||||
|
||||
BitMatrix bitMatrix = multiFormatWriter.encode(content, BarcodeFormat.QR_CODE, size, size, hints);
|
||||
return createBitmap(bitMatrix);
|
||||
} catch (WriterException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Bitmap createBitmap(BitMatrix matrix) {
|
||||
int width = matrix.getWidth();
|
||||
int height = matrix.getHeight();
|
||||
int[] pixels = new int[width * height];
|
||||
for (int y = 0; y < height; y++) {
|
||||
int offset = y * width;
|
||||
for (int x = 0; x < width; x++) {
|
||||
pixels[offset + x] = matrix.get(x, y) ? 0xFF000000 : 0x55FFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
// private View.OnClickListener onAddUser = new View.OnClickListener() {
|
||||
// @Override
|
||||
// public void onClick(View v) {
|
||||
//
|
||||
// User user = new User(1, "felixpk@outlook.de", MispRestClient.roleId.SYNC_USER.value());
|
||||
//
|
||||
// restClient.addUser(user, new MispRestClient.UserCallback() {
|
||||
// @Override
|
||||
// public void success(User user) {
|
||||
// resultView.setText(user.toString());
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void failure(String error) {
|
||||
// resultView.setText(error);
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// private View.OnClickListener onAddOrganisation = new View.OnClickListener() {
|
||||
// @Override
|
||||
// public void onClick(View v) {
|
||||
// Organisation organisation = new Organisation("API Organisation 2", "API Generated Organisation");
|
||||
// organisation.local = true;
|
||||
// organisation.nationality = "Buxdehude";
|
||||
//
|
||||
// restClient.addOrganisation(organisation, new MispRestClient.OrganisationCallback() {
|
||||
// @Override
|
||||
// public void success(Organisation organisation) {
|
||||
// resultView.setText(organisation.toString());
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void failure(String error) {
|
||||
// resultView.setText(error);
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// private View.OnClickListener onAddServer = new View.OnClickListener() {
|
||||
// @Override
|
||||
// public void onClick(View v) {
|
||||
//
|
||||
// Organisation organisation = new Organisation("", "");
|
||||
// Server server = new Server("API Remote Server", "https://127.0.0.1", "0000000000000000000000000000000000000000", 1);
|
||||
//
|
||||
// MispServer mispServer = new MispServer(server, organisation, organisation);
|
||||
//
|
||||
//
|
||||
// restClient.addServer(mispServer, new MispRestClient.ServerCallback() {
|
||||
// @Override
|
||||
// public void success(List<MispServer> servers) {
|
||||
//
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void success(MispServer server) {
|
||||
//
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void failure(String error) {
|
||||
//
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// private View.OnClickListener onGetServers = new View.OnClickListener() {
|
||||
// @Override
|
||||
// public void onClick(View v) {
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
package lu.circl.mispbump;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Switch;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(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);
|
||||
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,66 @@
|
|||
package lu.circl.mispbump.auxiliary;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Point;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.EncodeHintType;
|
||||
import com.google.zxing.MultiFormatWriter;
|
||||
import com.google.zxing.WriterException;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class QrCodeGenerator {
|
||||
|
||||
private Activity callingActivity;
|
||||
|
||||
public QrCodeGenerator(Activity callingActivity) {
|
||||
this.callingActivity = callingActivity;
|
||||
}
|
||||
|
||||
public Bitmap generateQrCode(String content) {
|
||||
Point displaySize = new Point();
|
||||
callingActivity.getWindowManager().getDefaultDisplay().getSize(displaySize);
|
||||
|
||||
int size = displaySize.x;
|
||||
|
||||
if (displaySize.x > displaySize.y) {
|
||||
size = displaySize.y;
|
||||
}
|
||||
|
||||
size = (int)(size * 0.8);
|
||||
|
||||
try {
|
||||
MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
|
||||
|
||||
Map<EncodeHintType, Integer> hints = new HashMap<>();
|
||||
hints.put(EncodeHintType.MARGIN, 0);
|
||||
|
||||
BitMatrix bitMatrix = multiFormatWriter.encode(content, BarcodeFormat.QR_CODE, size, size, hints);
|
||||
return createBitmap(bitMatrix);
|
||||
} catch (WriterException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Bitmap createBitmap(BitMatrix matrix) {
|
||||
int width = matrix.getWidth();
|
||||
int height = matrix.getHeight();
|
||||
int[] pixels = new int[width * height];
|
||||
for (int y = 0; y < height; y++) {
|
||||
int offset = y * width;
|
||||
for (int x = 0; x < width; x++) {
|
||||
pixels[offset + x] = matrix.get(x, y) ? 0xFF000000 : 0x99FFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
|
||||
return bitmap;
|
||||
}
|
||||
}
|
|
@ -18,7 +18,6 @@ import android.hardware.camera2.CaptureRequest;
|
|||
import android.hardware.camera2.params.StreamConfigurationMap;
|
||||
import android.media.Image;
|
||||
import android.media.ImageReader;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
|
@ -94,7 +93,7 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest
|
|||
if (barcodes.size() > 0) {
|
||||
if (qrResultCallback != null) {
|
||||
|
||||
qrResultCallback.qrDataResult(barcodes.valueAt(0).rawValue);
|
||||
qrResultCallback.qrScanResult(barcodes.valueAt(0).rawValue);
|
||||
|
||||
try {
|
||||
sleep(100);
|
||||
|
@ -103,7 +102,7 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest
|
|||
}
|
||||
|
||||
} else {
|
||||
Log.i(TAG, "QrResultCallback not attached");
|
||||
Log.i(TAG, "QrScanCallback not attached");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -121,7 +120,7 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest
|
|||
}
|
||||
}
|
||||
|
||||
private QrResultCallback qrResultCallback;
|
||||
private QrScanCallback qrResultCallback;
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
|
@ -783,8 +782,8 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest
|
|||
}
|
||||
|
||||
|
||||
public interface QrResultCallback {
|
||||
void qrDataResult(String qrData);
|
||||
public interface QrScanCallback {
|
||||
void qrScanResult(String qrData);
|
||||
}
|
||||
|
||||
private boolean readQrEnabled = true;
|
||||
|
@ -854,7 +853,7 @@ public class CameraFragment extends Fragment implements ActivityCompat.OnRequest
|
|||
}
|
||||
}
|
||||
|
||||
public void setOnQrAvailableListener(QrResultCallback callback) {
|
||||
public void setOnQrAvailableListener(QrScanCallback callback) {
|
||||
qrResultCallback = callback;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,18 +31,6 @@ import retrofit2.converter.gson.GsonConverterFactory;
|
|||
*/
|
||||
public class MispRestClient {
|
||||
|
||||
// callbacks and interfaces
|
||||
|
||||
public enum roleId {
|
||||
ADMIN(1), ORG_ADMIN(2), USER(3), PUBLISHER(4), SYNC_USER(5), READ_ONLY(6);
|
||||
|
||||
private final int id;
|
||||
|
||||
roleId(int id) { this.id = id; }
|
||||
|
||||
public int value() { return id; }
|
||||
}
|
||||
|
||||
public interface UserCallback {
|
||||
void success(User user);
|
||||
void failure(String error);
|
||||
|
@ -59,7 +47,6 @@ public class MispRestClient {
|
|||
void failure(String error);
|
||||
}
|
||||
|
||||
// fields
|
||||
|
||||
private PreferenceManager preferenceManager;
|
||||
private MispRestService mispRestService;
|
||||
|
|
|
@ -9,4 +9,7 @@ public class MispUser {
|
|||
@Expose
|
||||
public User user;
|
||||
|
||||
public MispUser(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
}
|
|
@ -5,6 +5,13 @@ import com.google.gson.annotations.SerializedName;
|
|||
|
||||
public class User {
|
||||
|
||||
public static final int ROLE_ADMIN = 1;
|
||||
public static final int ROLE_ORG_ADMIN = 2;
|
||||
public static final int ROLE_USER = 3;
|
||||
public static final int ROLE_PUBLISHER = 4;
|
||||
public static final int ROLE_SYNC_USER = 5;
|
||||
public static final int ROLE_READ_ONLY = 6;
|
||||
|
||||
public User(Integer org_id, String email, Integer role_id) {
|
||||
this.org_id = org_id;
|
||||
this.email = email;
|
||||
|
|
|
@ -5,6 +5,8 @@ import android.util.Base64;
|
|||
import javax.crypto.*;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.*;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
|
@ -14,7 +16,7 @@ public class AESSecurity {
|
|||
|
||||
private static final String TAG = "MISP_LOGGING";
|
||||
|
||||
private static final String ENCRYPT_ALGORITHM = "AES/CBC/PKCS5Padding";
|
||||
private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
|
||||
private static final String KEY_PAIR_ALGORITHM = "EC";
|
||||
private static final int KEY_SIZE = 521; // 224 | 256 | 384 | 521
|
||||
private static final String KEY_AGREEMENT_ALGORITHM = "ECDH";
|
||||
|
@ -28,16 +30,22 @@ public class AESSecurity {
|
|||
private byte[] sharedSecret;
|
||||
private IvParameterSpec ivParameterSpec;
|
||||
|
||||
public AESSecurity() {
|
||||
private AESSecurity() {
|
||||
initialize();
|
||||
}
|
||||
|
||||
public static AESSecurity getInstance() {
|
||||
if(instance == null) {
|
||||
instance = new AESSecurity();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a public and a private key using an elliptic curve algorithm.
|
||||
* The private key is fed into the key agreement instance.
|
||||
*/
|
||||
private void initialize() {
|
||||
|
||||
try {
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance(KEY_PAIR_ALGORITHM);
|
||||
kpg.initialize(KEY_SIZE);
|
||||
|
@ -58,7 +66,6 @@ public class AESSecurity {
|
|||
* @param publickey public key of the sync partner
|
||||
*/
|
||||
public void setForeignPublicKey(PublicKey publickey) {
|
||||
|
||||
try {
|
||||
keyAgreement.doPhase(publickey, true);
|
||||
|
||||
|
@ -74,53 +81,43 @@ public class AESSecurity {
|
|||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param data
|
||||
* @return
|
||||
* Encrypts data.
|
||||
* @param data data to encrypt
|
||||
* @return To String converted and encrypted data
|
||||
*/
|
||||
public String encrypt(String data) {
|
||||
try {
|
||||
Cipher c = Cipher.getInstance(CIPHER_ALGORITHM);
|
||||
c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(sharedSecret, CIPHER_ALGORITHM), ivParameterSpec);
|
||||
|
||||
Key key = generateKey();
|
||||
Cipher c = Cipher.getInstance(ENCRYPT_ALGORITHM);
|
||||
|
||||
try {
|
||||
c.init(Cipher.ENCRYPT_MODE, key, ivParameterSpec);
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
byte[] encVal = c.doFinal(data.getBytes());
|
||||
return Base64.encodeToString(encVal, 0);
|
||||
byte[] cipherText = c.doFinal(data.getBytes(StandardCharsets.UTF_8));
|
||||
return Base64.encodeToString(cipherText, Base64.NO_WRAP);
|
||||
|
||||
} catch (BadPaddingException | InvalidKeyException | NoSuchPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param data
|
||||
* @return
|
||||
* Decrypts data.
|
||||
* @param data data to decrypt
|
||||
* @return To String converted and decrypted data
|
||||
*/
|
||||
public String decrypt(String data) {
|
||||
try {
|
||||
Key key = generateKey();
|
||||
Cipher c = Cipher.getInstance(CIPHER_ALGORITHM);
|
||||
c.init(Cipher.DECRYPT_MODE, new SecretKeySpec(sharedSecret, CIPHER_ALGORITHM), ivParameterSpec);
|
||||
|
||||
Cipher c = Cipher.getInstance(ENCRYPT_ALGORITHM);
|
||||
byte[] cipherText = Base64.decode(data, Base64.NO_WRAP);
|
||||
return new String(c.doFinal(cipherText), StandardCharsets.UTF_8);
|
||||
|
||||
try {
|
||||
c.init(Cipher.DECRYPT_MODE, key, ivParameterSpec);
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
byte[] decoded = Base64.decode(data, 0);
|
||||
byte[] decValue = c.doFinal(decoded);
|
||||
return new String(decValue);
|
||||
} catch (BadPaddingException | InvalidKeyException | NoSuchPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
@ -129,31 +126,12 @@ public class AESSecurity {
|
|||
return publickey;
|
||||
}
|
||||
|
||||
private Key generateKey() {
|
||||
return new SecretKeySpec(sharedSecret, ENCRYPT_ALGORITHM);
|
||||
}
|
||||
|
||||
public static String publicKeyToString(PublicKey key) {
|
||||
return Base64.encodeToString(key.getEncoded(), Base64.DEFAULT);
|
||||
}
|
||||
|
||||
public static PublicKey publicKeyFromString(String key) {
|
||||
try {
|
||||
|
||||
byte[] input = Base64.decode(key, Base64.DEFAULT);
|
||||
return KeyFactory.getInstance(KEY_FACTORY_ALGORITHM).generatePublic(new X509EncodedKeySpec(input));
|
||||
|
||||
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static AESSecurity getInstance() {
|
||||
if(instance == null) {
|
||||
instance = new AESSecurity();
|
||||
}
|
||||
return instance;
|
||||
public static PublicKey publicKeyFromString(String key) throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
byte[] input = Base64.decode(key, Base64.DEFAULT);
|
||||
return KeyFactory.getInstance(KEY_FACTORY_ALGORITHM).generatePublic(new X509EncodedKeySpec(input));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,7 +72,6 @@ public class KeyStoreWrapper {
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @return SecretKey associated with the given alias.
|
||||
|
@ -155,11 +154,11 @@ public class KeyStoreWrapper {
|
|||
* Encrypt data with given algorithm and key associated with alias.
|
||||
* @param data data to encrypt.
|
||||
* @return encrypted data as String.
|
||||
* @throws NoSuchPaddingException
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws InvalidKeyException
|
||||
* @throws BadPaddingException
|
||||
* @throws IllegalBlockSizeException
|
||||
* @throws NoSuchPaddingException padding not found
|
||||
* @throws NoSuchAlgorithmException algorithm not found
|
||||
* @throws InvalidKeyException invalid key
|
||||
* @throws BadPaddingException bad padding
|
||||
* @throws IllegalBlockSizeException illegal block size
|
||||
*/
|
||||
public String encrypt(String data) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
|
||||
SecretKey secretKey;
|
||||
|
@ -184,12 +183,12 @@ public class KeyStoreWrapper {
|
|||
* Decrypts data with given algorithm and key associated with alias.
|
||||
* @param input encrypted data.
|
||||
* @return decrypted data as String.
|
||||
* @throws NoSuchPaddingException
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws InvalidAlgorithmParameterException
|
||||
* @throws InvalidKeyException
|
||||
* @throws BadPaddingException
|
||||
* @throws IllegalBlockSizeException
|
||||
* @throws NoSuchPaddingException padding not found
|
||||
* @throws NoSuchAlgorithmException algorithm not found
|
||||
* @throws InvalidAlgorithmParameterException invalid algorithm parameters
|
||||
* @throws InvalidKeyException invalid key
|
||||
* @throws BadPaddingException bad padding
|
||||
* @throws IllegalBlockSizeException illegal block size
|
||||
*/
|
||||
public String decrypt(String input) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
|
||||
|
||||
|
@ -236,4 +235,18 @@ public class KeyStoreWrapper {
|
|||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine IV and encrypted data.
|
||||
* @param iv initialisation vector
|
||||
* @param encryptedData encrypted data
|
||||
* @return combination of iv and encrypted data
|
||||
*/
|
||||
private static byte[] getCombinedArray(byte[] iv, byte[] encryptedData) {
|
||||
byte[] combined = new byte[iv.length + encryptedData.length];
|
||||
for (int i = 0; i < combined.length; ++i) {
|
||||
combined[i] = i < iv.length ? iv[i] : encryptedData[i - iv.length];
|
||||
}
|
||||
return combined;
|
||||
}
|
||||
}
|
|
@ -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="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
|
||||
</vector>
|
|
@ -1,59 +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=".AESActivity">
|
||||
|
||||
<android.support.v7.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"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/aes_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="Button"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/aes_button"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/toolbar">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/aes_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="TextView"
|
||||
tools:layout_editor_absoluteX="16dp"
|
||||
tools:layout_editor_absoluteY="659dp" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
|
@ -14,10 +14,22 @@
|
|||
android:background="?attr/colorPrimary"
|
||||
android:elevation="4dp"
|
||||
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.Light" />
|
||||
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
android:id="@+id/continue_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" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/sync_fragment_container"
|
||||
|
@ -32,19 +44,9 @@
|
|||
android:id="@+id/qrcode"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/sync_qr_container"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:contentDescription="QrCode" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/sync_qr_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:contentDescription="QrCode"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/toolbar">
|
||||
</FrameLayout>
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</android.support.constraint.ConstraintLayout>
|
|
@ -0,0 +1,164 @@
|
|||
<?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">
|
||||
|
||||
<!-- Share events preference -->
|
||||
|
||||
<TextView
|
||||
android:id="@+id/share_events_title"
|
||||
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:textColor="@android:color/primary_text_light"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/share_events_switch"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/share_events_desc"
|
||||
android:layout_width="0dp"
|
||||
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:textSize="12sp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/share_events_switch"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/share_events_title" />
|
||||
|
||||
<Switch
|
||||
android:id="@+id/share_events_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" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/push_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:fontFamily="sans-serif"
|
||||
android:text="Push"
|
||||
android:textColor="#000000"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/push_switch"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/share_events_desc" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/push_desc"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="Allow the upload of events and their attributes"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/push_switch"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/push_title" />
|
||||
|
||||
|
||||
<Switch
|
||||
android:id="@+id/push_switch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/push_desc"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/push_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pull_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="Pull"
|
||||
android:textColor="#000000"
|
||||
app:layout_constraintEnd_toStartOf="@+id/pull_switch"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/push_desc" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pull_desc"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="Allow the download of events and their attributes"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/pull_switch"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/pull_title" />
|
||||
|
||||
<Switch
|
||||
android:id="@+id/pull_switch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/pull_desc"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/pull_title" />
|
||||
|
||||
<Switch
|
||||
android:id="@+id/cache_switch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/cache_desc"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/cache_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/cache_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="Caching"
|
||||
android:textColor="#000000"
|
||||
app:layout_constraintEnd_toStartOf="@+id/cache_switch"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/pull_desc" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/cache_desc"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="Allow the caching of events and their attributes"
|
||||
android:textSize="12sp"
|
||||
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>
|
|
@ -9,4 +9,7 @@
|
|||
<string name="save_automation_key_hint">Save Automation Key</string>
|
||||
<string name="home">Home</string>
|
||||
<string name="menu_login_help_label">Help</string>
|
||||
|
||||
// Sync options preferences
|
||||
<string name="sync_option_share"><u>Share events</u>\nThis will generate a sync server on your instance</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in New Issue