@ -0,0 +1,12 @@ |
||||
package io.stormbird.wallet.entity; |
||||
|
||||
/** |
||||
* Created by James on 9/06/2019. |
||||
* Stormbird in Sydney |
||||
*/ |
||||
|
||||
public interface AuthenticationCallback |
||||
{ |
||||
void authenticatePass(int callbackId); |
||||
void authenticateFail(String fail); |
||||
} |
@ -0,0 +1,400 @@ |
||||
package io.stormbird.wallet.service; |
||||
|
||||
import android.annotation.TargetApi; |
||||
import android.app.Activity; |
||||
import android.content.Context; |
||||
import android.os.Build; |
||||
import android.security.keystore.*; |
||||
import android.util.Log; |
||||
import io.stormbird.token.tools.Numeric; |
||||
import io.stormbird.wallet.R; |
||||
import io.stormbird.wallet.entity.AuthenticationCallback; |
||||
import io.stormbird.wallet.entity.ServiceErrorException; |
||||
import io.stormbird.wallet.widget.SignTransactionDialog; |
||||
import org.web3j.crypto.Hash; |
||||
import wallet.core.jni.CoinType; |
||||
import wallet.core.jni.HDWallet; |
||||
import wallet.core.jni.PrivateKey; |
||||
import wallet.core.jni.PublicKey; |
||||
|
||||
import javax.crypto.*; |
||||
import javax.crypto.spec.IvParameterSpec; |
||||
import java.io.*; |
||||
import java.security.*; |
||||
import java.security.cert.CertificateException; |
||||
import java.security.spec.ECGenParameterSpec; |
||||
|
||||
import static io.stormbird.wallet.entity.ServiceErrorException.*; |
||||
|
||||
@TargetApi(23) |
||||
public class HDKeyService implements AuthenticationCallback |
||||
{ |
||||
private static final String TAG = "HDWallet"; |
||||
private static final String ANDROID_KEY_STORE = "AndroidKeyStore"; |
||||
private static final String UNLOCK_KEY = "unlock_key"; |
||||
private static final String BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC; |
||||
private static final String PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7; |
||||
private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS7Padding"; |
||||
private static final int AUTHENTICATION_DURATION_SECONDS = 30; |
||||
|
||||
private enum Operation |
||||
{ |
||||
CREATE_HD_KEY, UNLOCK_HD_KEY |
||||
} |
||||
|
||||
private static final int DEFAULT_KEY_STRENGTH = 128; |
||||
private final Activity context; |
||||
|
||||
private HDWallet currentWallet; |
||||
private String currentKey; |
||||
private SignTransactionDialog signDialog; |
||||
|
||||
public HDKeyService(Activity ctx) |
||||
{ |
||||
System.loadLibrary("TrustWalletCore"); |
||||
context = ctx; |
||||
} |
||||
|
||||
//Create HDkey
|
||||
public String createNewHDKey() |
||||
{ |
||||
currentWallet = new HDWallet(DEFAULT_KEY_STRENGTH, "key1"); |
||||
String mnemonic = currentWallet.mnemonic(); |
||||
checkAuthentication(Operation.CREATE_HD_KEY); |
||||
PrivateKey pk = currentWallet.getKeyForCoin(CoinType.ETHEREUM); |
||||
return CoinType.ETHEREUM.deriveAddress(pk); |
||||
} |
||||
|
||||
private void createHDKey2() |
||||
{ |
||||
storeHDWallet(currentWallet); |
||||
} |
||||
|
||||
public byte[] signData(String key, byte[] data) |
||||
{ |
||||
//unlock
|
||||
return null; |
||||
} |
||||
|
||||
private synchronized byte[] signData(String data, String keyAddress) throws ServiceErrorException |
||||
{ |
||||
KeyStore keyStore; |
||||
try |
||||
{ |
||||
keyStore = KeyStore.getInstance(ANDROID_KEY_STORE); |
||||
keyStore.load(null); |
||||
if (!keyStore.containsAlias(keyAddress)) return null; |
||||
String encryptedDataFilePath = getFilePath(context, keyAddress + "hd"); |
||||
SecretKey secretKey = (SecretKey) keyStore.getKey(keyAddress, null); |
||||
if (secretKey == null) |
||||
{ |
||||
/* no such key, the key is just simply not there */ |
||||
boolean fileExists = new File(encryptedDataFilePath).exists(); |
||||
if (!fileExists) |
||||
{ |
||||
return null;/* file also not there, fine then */ |
||||
} |
||||
throw new ServiceErrorException( |
||||
KEY_IS_GONE, |
||||
"file is present but the key is gone: " + keyAddress); |
||||
} |
||||
|
||||
boolean ivExists = new File(getFilePath(context, currentKey + "iv")).exists(); |
||||
byte[] iv = null; |
||||
|
||||
if (ivExists) iv = readBytesFromFile(getFilePath(context, currentKey + "iv")); |
||||
if (iv == null || iv.length == 0) |
||||
{ |
||||
throw new NullPointerException("iv is missing for " + currentKey + "iv"); |
||||
} |
||||
Cipher outCipher = Cipher.getInstance(CIPHER_ALGORITHM); |
||||
outCipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv)); |
||||
CipherInputStream cipherInputStream = new CipherInputStream(new FileInputStream(encryptedDataFilePath), outCipher); |
||||
byte[] mnemonicBytes = readBytesFromStream(cipherInputStream); |
||||
String mnemonic = new String(mnemonicBytes); |
||||
System.out.println(mnemonic); |
||||
return null; |
||||
} |
||||
catch (InvalidKeyException e) |
||||
{ |
||||
if (e instanceof UserNotAuthenticatedException) |
||||
{ |
||||
// showAuthenticationScreen(context, requestCode);
|
||||
throw new ServiceErrorException(USER_NOT_AUTHENTICATED); |
||||
} |
||||
else |
||||
{ |
||||
throw new ServiceErrorException(INVALID_KEY); |
||||
} |
||||
} |
||||
catch (IOException | CertificateException | KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException e) |
||||
{ |
||||
throw new ServiceErrorException(KEY_STORE_ERROR); |
||||
} |
||||
} |
||||
|
||||
private synchronized boolean storeHDWallet(HDWallet wallet) |
||||
{ |
||||
KeyStore keyStore; |
||||
try |
||||
{ |
||||
keyStore = KeyStore.getInstance(ANDROID_KEY_STORE); |
||||
keyStore.load(null); |
||||
PrivateKey pk = currentWallet.getKeyForCoin(CoinType.ETHEREUM); |
||||
currentKey = CoinType.ETHEREUM.deriveAddress(pk); |
||||
|
||||
if (keyStore.containsAlias(currentKey)) { keyStore.deleteEntry(currentKey); } //TODO: Don't allow this
|
||||
|
||||
KeyGenerator keyGenerator = getMaxSecurityKeyGenerator(); |
||||
|
||||
// Set the alias of the entry in Android KeyStore where the key will appear
|
||||
// and the constrains (purposes) in the constructor of the Builder
|
||||
|
||||
final SecretKey secretKey = keyGenerator.generateKey(); |
||||
final Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); |
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey); |
||||
String path = getFilePath(context, currentKey + "hd"); |
||||
Cipher inCipher = Cipher.getInstance(CIPHER_ALGORITHM); |
||||
inCipher.init(Cipher.ENCRYPT_MODE, secretKey); |
||||
byte[] iv = inCipher.getIV(); |
||||
String ivPath = getFilePath(context, currentKey + "iv"); |
||||
boolean success = writeBytesToFile(ivPath, iv); |
||||
if (!success) { |
||||
keyStore.deleteEntry(currentKey + "iv"); |
||||
throw new ServiceErrorException( |
||||
ServiceErrorException.FAIL_TO_SAVE_IV_FILE, |
||||
"Failed to saveTokens the iv file for: " + currentKey + "iv"); |
||||
} |
||||
|
||||
try (CipherOutputStream cipherOutputStream = new CipherOutputStream( |
||||
new FileOutputStream(path), |
||||
inCipher)) |
||||
{ |
||||
cipherOutputStream.write(wallet.mnemonic().getBytes()); |
||||
} |
||||
catch (Exception ex) |
||||
{ |
||||
throw new ServiceErrorException( |
||||
ServiceErrorException.KEY_STORE_ERROR, |
||||
"Failed to saveTokens the file for: " + currentKey); |
||||
} |
||||
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance(secretKey.getAlgorithm(), ANDROID_KEY_STORE); |
||||
KeyInfo keyInfo = (KeyInfo)factory.getKeySpec(secretKey, KeyInfo.class); |
||||
|
||||
if (keyInfo.isInsideSecureHardware()) |
||||
{ |
||||
System.out.println("Using hardware protected key"); |
||||
} |
||||
else |
||||
{ |
||||
System.out.println("Not using hardware protected key"); |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
catch (UserNotAuthenticatedException e) |
||||
{ |
||||
|
||||
} |
||||
catch (Exception ex) |
||||
{ |
||||
Log.d(TAG, "Key store error", ex); |
||||
//throw new ServiceErrorException(KEY_STORE_ERROR);
|
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
private KeyGenerator getMaxSecurityKeyGenerator() |
||||
{ |
||||
KeyGenerator keyGenerator; |
||||
|
||||
try |
||||
{ |
||||
keyGenerator = KeyGenerator.getInstance( |
||||
KeyProperties.KEY_ALGORITHM_AES, |
||||
ANDROID_KEY_STORE); |
||||
} |
||||
catch (NoSuchAlgorithmException|NoSuchProviderException ex) |
||||
{ |
||||
ex.printStackTrace(); |
||||
return null; |
||||
} |
||||
|
||||
try |
||||
{ |
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && tryInitStrongBoxKey(keyGenerator)) |
||||
{ |
||||
Log.d(TAG, "Using Strongbox"); |
||||
} |
||||
else |
||||
{ |
||||
//fallback to non Strongbox
|
||||
tryInitUserAuthenticatedKey(keyGenerator); |
||||
} |
||||
} |
||||
catch (InvalidAlgorithmParameterException e) |
||||
{ |
||||
e.printStackTrace(); |
||||
keyGenerator = null; |
||||
} |
||||
catch (Exception e) |
||||
{ |
||||
e.printStackTrace(); |
||||
keyGenerator = null; |
||||
} |
||||
|
||||
return keyGenerator; |
||||
} |
||||
|
||||
private boolean tryInitStrongBoxKey(KeyGenerator keyGenerator) throws InvalidAlgorithmParameterException |
||||
{ |
||||
try |
||||
{ |
||||
keyGenerator.init(new KeyGenParameterSpec.Builder( |
||||
currentKey, |
||||
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) |
||||
.setBlockModes(BLOCK_MODE) |
||||
.setKeySize(256) |
||||
.setUserAuthenticationRequired(true) |
||||
.setIsStrongBoxBacked(true) |
||||
.setUserAuthenticationValidityDurationSeconds(AUTHENTICATION_DURATION_SECONDS) |
||||
.setRandomizedEncryptionRequired(true) |
||||
.setEncryptionPaddings(PADDING) |
||||
.build()); |
||||
} |
||||
catch (StrongBoxUnavailableException e) |
||||
{ |
||||
Log.d(TAG, "Android 9 device doesn't have StrongBox"); |
||||
return false; |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
private void tryInitUserAuthenticatedKey(KeyGenerator keyGenerator) throws InvalidAlgorithmParameterException |
||||
{ |
||||
keyGenerator.init(new KeyGenParameterSpec.Builder( |
||||
currentKey, |
||||
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) |
||||
.setBlockModes(BLOCK_MODE) |
||||
.setKeySize(256) |
||||
.setUserAuthenticationRequired(true) |
||||
.setUserAuthenticationValidityDurationSeconds(AUTHENTICATION_DURATION_SECONDS) |
||||
.setRandomizedEncryptionRequired(true) |
||||
.setEncryptionPaddings(PADDING) |
||||
.build()); |
||||
} |
||||
|
||||
|
||||
private void checkAuthentication(Operation operation) |
||||
{ |
||||
signDialog = new SignTransactionDialog(context, operation.ordinal()); |
||||
signDialog.setBigText("Authenticate Credentials"); |
||||
signDialog.setSecondaryButtonText(R.string.action_cancel); |
||||
signDialog.setPrimaryButtonText(R.string.dialog_title_sign_message); |
||||
signDialog.setCanceledOnTouchOutside(true); |
||||
signDialog.setOnCancelListener(v -> { authenticateFail("Cancelled"); }); |
||||
signDialog.show(); |
||||
signDialog.getFingerprintAuthorisation(this); |
||||
} |
||||
|
||||
private synchronized static String getFilePath(Context context, String fileName) { |
||||
return new File(context.getFilesDir(), fileName).getAbsolutePath(); |
||||
} |
||||
|
||||
private static boolean writeBytesToFile(String path, byte[] data) { |
||||
FileOutputStream fos = null; |
||||
try { |
||||
File file = new File(path); |
||||
fos = new FileOutputStream(file); |
||||
// Writes bytes from the specified byte array to this file output stream
|
||||
fos.write(data); |
||||
return true; |
||||
} catch (FileNotFoundException e) { |
||||
System.out.println("File not found" + e); |
||||
} catch (IOException ioe) { |
||||
System.out.println("Exception while writing file " + ioe); |
||||
} finally { |
||||
// close the streams using close method
|
||||
try { |
||||
if (fos != null) { |
||||
fos.close(); |
||||
} |
||||
} catch (IOException ioe) { |
||||
System.out.println("Error while closing stream: " + ioe); |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
private static byte[] readBytesFromFile(String path) { |
||||
byte[] bytes = null; |
||||
FileInputStream fin; |
||||
try { |
||||
File file = new File(path); |
||||
fin = new FileInputStream(file); |
||||
bytes = readBytesFromStream(fin); |
||||
} catch (IOException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
return bytes; |
||||
} |
||||
|
||||
|
||||
private static byte[] readBytesFromStream(InputStream in) { |
||||
// this dynamically extends to take the bytes you read
|
||||
ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream(); |
||||
// this is storage overwritten on each iteration with bytes
|
||||
int bufferSize = 1024; |
||||
byte[] buffer = new byte[bufferSize]; |
||||
// we need to know how may bytes were read to write them to the byteBuffer
|
||||
int len; |
||||
try { |
||||
while ((len = in.read(buffer)) != -1) { |
||||
byteBuffer.write(buffer, 0, len); |
||||
} |
||||
} catch (IOException e) { |
||||
e.printStackTrace(); |
||||
} finally { |
||||
try { |
||||
byteBuffer.close(); |
||||
} catch (IOException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
if (in != null) try { |
||||
in.close(); |
||||
} catch (IOException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
} |
||||
// and then we can return your byte array.
|
||||
return byteBuffer.toByteArray(); |
||||
} |
||||
|
||||
@Override |
||||
public void authenticatePass(int callbackId) |
||||
{ |
||||
signDialog.dismiss(); |
||||
//resume key operation
|
||||
Operation operation = Operation.values()[callbackId]; |
||||
switch (operation) |
||||
{ |
||||
case CREATE_HD_KEY: |
||||
createHDKey2(); |
||||
break; |
||||
case UNLOCK_HD_KEY: |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void authenticateFail(String fail) |
||||
{ |
||||
signDialog.dismiss(); |
||||
//TODO: display fail dialog
|
||||
} |
||||
} |
@ -0,0 +1,21 @@ |
||||
package io.stormbird.wallet.ui; |
||||
|
||||
import android.os.Bundle; |
||||
import android.support.annotation.Nullable; |
||||
import io.stormbird.wallet.R; |
||||
|
||||
public class BackupSeedPhrase extends BaseActivity |
||||
{ |
||||
|
||||
@Override |
||||
protected void onCreate(@Nullable Bundle savedInstanceState) |
||||
{ |
||||
super.onCreate(savedInstanceState); |
||||
|
||||
setContentView(R.layout.activity_backup_seed); |
||||
|
||||
toolbar(); |
||||
|
||||
setTitle(R.string.backup_seed_phrase); |
||||
} |
||||
} |
@ -1,48 +0,0 @@ |
||||
package io.stormbird.wallet.util; |
||||
|
||||
import android.content.Context; |
||||
import android.os.Build; |
||||
|
||||
import io.stormbird.wallet.entity.ServiceErrorException; |
||||
import com.wallet.pwd.trustapp.PasswordManager; |
||||
|
||||
import java.security.SecureRandom; |
||||
|
||||
public class PasswordStoreFactory { |
||||
public static void put(Context context, String address, String password) throws ServiceErrorException { |
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
||||
KS.put(context, address, password); |
||||
} else { |
||||
try { |
||||
PasswordManager.setPassword(address, password, context); |
||||
} catch (Exception e) { |
||||
throw new ServiceErrorException(ServiceErrorException.KEY_STORE_ERROR); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public static byte[] get(Context context, String address) throws ServiceErrorException { |
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
||||
return KS.get(context, address); |
||||
} else { |
||||
try { |
||||
return PasswordManager.getPassword(address, context).getBytes(); |
||||
} catch (Exception e) { |
||||
throw new ServiceErrorException(ServiceErrorException.KEY_STORE_ERROR); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public static void showAuthenticationScreen(Context context, int unlockScreenRequest) { |
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
||||
KS.showAuthenticationScreen(context, unlockScreenRequest); |
||||
} |
||||
} |
||||
|
||||
public static String generatePassword() { |
||||
byte bytes[] = new byte[256]; |
||||
SecureRandom random = new SecureRandom(); |
||||
random.nextBytes(bytes); |
||||
return String.valueOf(bytes); |
||||
} |
||||
} |
@ -0,0 +1,120 @@ |
||||
package io.stormbird.wallet.widget; |
||||
|
||||
import android.Manifest; |
||||
import android.app.Activity; |
||||
import android.app.KeyguardManager; |
||||
import android.content.Context; |
||||
import android.content.Intent; |
||||
import android.content.pm.PackageManager; |
||||
import android.hardware.fingerprint.FingerprintManager; |
||||
import android.os.Build; |
||||
import android.os.CancellationSignal; |
||||
import android.support.annotation.NonNull; |
||||
import android.view.View; |
||||
import android.widget.Button; |
||||
import android.widget.ImageView; |
||||
import android.widget.Toast; |
||||
import io.stormbird.wallet.R; |
||||
import io.stormbird.wallet.entity.AuthenticationCallback; |
||||
|
||||
/** |
||||
* Created by James on 7/06/2019. |
||||
* Stormbird in Sydney |
||||
*/ |
||||
public class SignTransactionDialog extends AWalletConfirmationDialog |
||||
{ |
||||
private int callBackId; |
||||
public SignTransactionDialog(@NonNull Activity activity, int callBackId) |
||||
{ |
||||
super(activity); |
||||
//display fingerprint
|
||||
ImageView fingerPrint = findViewById(R.id.image_fingerprint); |
||||
fingerPrint.setVisibility(View.VISIBLE); |
||||
findViewById(R.id.dialog_button1_container).setVisibility(View.GONE); |
||||
this.callBackId = callBackId; |
||||
} |
||||
|
||||
//TODO: Needs to be 'use PIN'
|
||||
@Override |
||||
public void setPrimaryButtonText(int resId) |
||||
{ |
||||
btnPrimary.setText(context.getResources().getString(resId)); |
||||
} |
||||
|
||||
//get fingerprint or PIN
|
||||
public void getFingerprintAuthorisation(AuthenticationCallback authCallback) { |
||||
// Create the Confirm Credentials screen. You can customize the title and description. Or
|
||||
// we will provide a generic one for you if you leave it null
|
||||
KeyguardManager km = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); |
||||
FingerprintManager fpManager = fingerprintUnlockSupported(context); |
||||
|
||||
if (fpManager != null) |
||||
{ |
||||
authenticate(fpManager, context, authCallback); |
||||
} |
||||
else |
||||
{ |
||||
//remove fingerprint
|
||||
ImageView fingerPrint = findViewById(R.id.image_fingerprint); |
||||
fingerPrint.setVisibility(View.GONE); |
||||
Button pin = findViewById(R.id.dialog_button1_container); |
||||
pin.setVisibility(View.VISIBLE); |
||||
} |
||||
// Intent intent = km.createConfirmDeviceCredentialIntent(null, null);
|
||||
// if (intent != null) {
|
||||
// Activity app = (Activity) context;
|
||||
// app.startActivityForResult(intent, REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS);
|
||||
// }
|
||||
} |
||||
|
||||
private void authenticate(FingerprintManager fpManager, Context context, AuthenticationCallback authCallback) { |
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) |
||||
return; |
||||
CancellationSignal cancellationSignal; |
||||
cancellationSignal = new CancellationSignal(); |
||||
fpManager.authenticate(null, cancellationSignal, 0, new FingerprintManager.AuthenticationCallback() { |
||||
@Override |
||||
public void onAuthenticationError(int errorCode, CharSequence errString) { |
||||
super.onAuthenticationError(errorCode, errString); |
||||
Toast.makeText(context, errString, Toast.LENGTH_SHORT).show(); |
||||
authCallback.authenticateFail(errString.toString()); |
||||
} |
||||
|
||||
@Override |
||||
public void onAuthenticationHelp(int helpCode, CharSequence helpString) { |
||||
super.onAuthenticationHelp(helpCode, helpString); |
||||
//Toast.makeText(context, helpString.toString(), Toast.LENGTH_SHORT).show();
|
||||
} |
||||
|
||||
@Override |
||||
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) { |
||||
super.onAuthenticationSucceeded(result); |
||||
authCallback.authenticatePass(callBackId); |
||||
} |
||||
|
||||
@Override |
||||
public void onAuthenticationFailed() { |
||||
super.onAuthenticationFailed(); |
||||
authCallback.authenticateFail("Authentication Failure"); |
||||
} |
||||
}, null); |
||||
} |
||||
|
||||
private static FingerprintManager fingerprintUnlockSupported(Context ctx) |
||||
{ |
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { |
||||
return null; |
||||
} |
||||
if (ctx.checkSelfPermission(Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) { |
||||
return null; |
||||
} |
||||
|
||||
FingerprintManager fpManager = (FingerprintManager) ctx.getSystemService(Context.FINGERPRINT_SERVICE); |
||||
if (fpManager == null || !fpManager.isHardwareDetected() || !fpManager.hasEnrolledFingerprints()) { |
||||
return null; |
||||
} |
||||
|
||||
return fpManager; |
||||
} |
||||
|
||||
} |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 79 KiB |
After Width: | Height: | Size: 9.3 KiB |
After Width: | Height: | Size: 107 KiB |
@ -0,0 +1,6 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" |
||||
android:shape="rectangle"> |
||||
<solid android:color="@color/warning_red" /> |
||||
<corners android:radius="2dp" /> |
||||
</shape> |
@ -0,0 +1,5 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"> |
||||
<item android:drawable="@drawable/background_warning_event" android:state_pressed="true"/> |
||||
<item android:drawable="@drawable/background_warning_event"/> |
||||
</selector> |
@ -0,0 +1,81 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<LinearLayout 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" |
||||
android:background="@color/colorPrimaryDark" |
||||
android:orientation="vertical"> |
||||
|
||||
<include layout="@layout/layout_simple_toolbar" /> |
||||
|
||||
<RelativeLayout |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
android:background="@drawable/background_card"> |
||||
|
||||
<ImageView |
||||
android:id="@+id/seed_image" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:layout_centerHorizontal="true" |
||||
android:layout_centerInParent="true" |
||||
android:background="@drawable/seed_graphic" /> |
||||
|
||||
<TextView |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:layout_gravity="center_horizontal" |
||||
android:fontFamily="@font/font_light" |
||||
android:layout_centerHorizontal="true" |
||||
android:layout_above="@id/seed_image" |
||||
android:gravity="center_horizontal" |
||||
android:layout_marginBottom="50dp" |
||||
android:text="Back up your wallet\nwith seed phrase" |
||||
android:textColor="@color/black" |
||||
android:textSize="28sp" /> |
||||
|
||||
<TextView |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:layout_below="@id/seed_image" |
||||
android:layout_centerHorizontal="true" |
||||
android:layout_marginTop="20dp" |
||||
android:gravity="center_horizontal" |
||||
android:text="Making backup is very simple and safe:\njust write down 12 words and\nkeep them in a safe place, offline." /> |
||||
|
||||
<Button |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:background="@color/nasty_green" |
||||
android:layout_marginEnd="16dp" |
||||
android:layout_marginStart="16dp" |
||||
android:layout_marginBottom="25dp" |
||||
android:text="back up my wallet" |
||||
android:textStyle="normal" |
||||
android:textColor="@color/white" |
||||
android:letterSpacing="0.04" |
||||
android:layout_alignParentBottom="true" |
||||
android:textSize="16sp" |
||||
android:textAllCaps="true" /> |
||||
|
||||
|
||||
<io.stormbird.wallet.widget.ProgressView |
||||
android:id="@+id/progress_view" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
android:layout_alignParentEnd="true" |
||||
android:layout_alignParentTop="true" |
||||
tools:visibility="gone" /> |
||||
|
||||
</RelativeLayout> |
||||
|
||||
<io.stormbird.wallet.widget.SystemView |
||||
android:id="@+id/system_view" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
android:layout_marginTop="?actionBarSize" |
||||
android:background="@color/white" |
||||
tools:visibility="gone" /> |
||||
|
||||
</LinearLayout> |
@ -1,6 +1,6 @@ |
||||
#Sun Apr 14 18:17:45 AEST 2019 |
||||
#Wed Jul 10 11:34:33 AEST 2019 |
||||
distributionBase=GRADLE_USER_HOME |
||||
distributionPath=wrapper/dists |
||||
zipStoreBase=GRADLE_USER_HOME |
||||
zipStorePath=wrapper/dists |
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip |
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip |
||||
|