From 6216ac2bd378aba4e432f1c24561f7ae362592ff Mon Sep 17 00:00:00 2001 From: 4f77616973 <0x4f@tuta.io> Date: Thu, 12 Jan 2023 19:20:34 +0530 Subject: [PATCH 01/46] added 19 digit card support --- .../main/kotlin/cloud/keyspace/android/AddCard.kt | 4 +++- app/src/main/res/layout/card.xml | 15 ++++++++------- app/src/main/res/layout/edit_card.xml | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/app/src/main/kotlin/cloud/keyspace/android/AddCard.kt b/app/src/main/kotlin/cloud/keyspace/android/AddCard.kt index 7fdbfd4..01abb24 100644 --- a/app/src/main/kotlin/cloud/keyspace/android/AddCard.kt +++ b/app/src/main/kotlin/cloud/keyspace/android/AddCard.kt @@ -372,7 +372,9 @@ class AddCard : AppCompatActivity() { vault.card?.remove(io.getCard(itemId!!, vault)) } - if (cardNumberInput.text.toString().length < 16) cardNumberInput.error = "Enter a valid 16 digit card number" + if (cardNumberInput.text.toString().replace(" ", "").length < 16) cardNumberInput.error = "Enter a valid 16 digit card number" + else if (cardNumberInput.text.toString().replace(" ", "").length in 17..18 + || cardNumberInput.text.toString().replace(" ", "").length > 19) cardNumberInput.error = "Enter a valid 19 digit card number" else if (securityCode.text.toString().length !in 3..4) securityCode.error = "Enter a valid security code" else if (toDate.text.toString().isEmpty()) toDate.error = "Enter an expiry date" else if (cardholderNameInput.text.toString().isEmpty()) cardholderNameInput.error = "Enter card holder's name" diff --git a/app/src/main/res/layout/card.xml b/app/src/main/res/layout/card.xml index 1abb09d..74f2e09 100644 --- a/app/src/main/res/layout/card.xml +++ b/app/src/main/res/layout/card.xml @@ -405,15 +405,15 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:fontFamily="@font/beam" - android:letterSpacing="0.2" - android:maxLength="19" + android:letterSpacing="0.175" + android:maxLength="23" android:paddingHorizontal="20dp" - android:scaleY="1.25" - android:singleLine="true" + android:scaleY="1.3" + android:singleLine="false" android:supportsRtl="false" - android:text="1234 5678 9012 3456" + android:text="1234 5678 9012 3456 123" android:textIsSelectable="true" - android:textSize="21sp" + android:textSize="20sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" @@ -429,6 +429,7 @@ android:letterSpacing="0.15" android:maxLength="20" android:paddingHorizontal="20dp" + android:scaleY="1.1" android:singleLine="true" android:supportsRtl="false" android:text="Clayton Bigsby" @@ -480,7 +481,7 @@ android:fontFamily="@font/beam" android:letterSpacing="0.25" android:maxLength="5" - android:scaleY="1.25" + android:scaleY="1.3" android:supportsRtl="false" android:text="01/25" android:textIsSelectable="true" diff --git a/app/src/main/res/layout/edit_card.xml b/app/src/main/res/layout/edit_card.xml index aa57895..1444660 100644 --- a/app/src/main/res/layout/edit_card.xml +++ b/app/src/main/res/layout/edit_card.xml @@ -211,7 +211,7 @@ android:digits="0123456789 " android:inputType="phone" android:letterSpacing="0.05" - android:maxLength="19" /> + android:maxLength="23" /> From 74e92f4968290da59540abe7a2fc9f822616898d Mon Sep 17 00:00:00 2001 From: 4f77616973 <0x4f@tuta.io> Date: Thu, 12 Jan 2023 19:25:27 +0530 Subject: [PATCH 02/46] improved payment gateway detection --- .../cloud/keyspace/android/MiscUtilities.kt | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/app/src/main/kotlin/cloud/keyspace/android/MiscUtilities.kt b/app/src/main/kotlin/cloud/keyspace/android/MiscUtilities.kt index 64647b9..931e636 100644 --- a/app/src/main/kotlin/cloud/keyspace/android/MiscUtilities.kt +++ b/app/src/main/kotlin/cloud/keyspace/android/MiscUtilities.kt @@ -36,32 +36,39 @@ class MiscUtilities (applicationContext: Context) { fun getPaymentGateway(cardNumber: String): String? { try { if (cardNumber.startsWith("4") || - cardNumber.startsWith("4026") || - cardNumber.startsWith("411750") || - cardNumber.startsWith("4508") || - cardNumber.startsWith("4913") || - cardNumber.startsWith("4917") || - cardNumber.startsWith("4844")) { + cardNumber.startsWith("40") || + cardNumber.startsWith("411") || + cardNumber.startsWith("45") || + cardNumber.startsWith("49") || + cardNumber.startsWith("49") || + cardNumber.startsWith("48") + ) { return "visa" } else if ( (cardNumber.take(4)).toInt() in 2221..2720 || (cardNumber.take(2)).toInt() in 51..55 || - (cardNumber.take(4)).toInt() in 5100..5399) { + (cardNumber.take(4)).toInt() in 5100..5399 || + cardNumber.startsWith("67") + ) { return "mastercard" } else if ( (cardNumber.take(4)).toInt() in 622126..622925 || (cardNumber.take(3)).toInt() in 644..649 || - cardNumber.startsWith("66")) { + cardNumber.startsWith("66") || + cardNumber.startsWith("601") + ) { return "discover" } else if ( cardNumber.startsWith("60") || cardNumber.startsWith("6521") || cardNumber.startsWith("6522") || - cardNumber.startsWith("50")) { + cardNumber.startsWith("50") + ) { return "rupay" } else if ( cardNumber.startsWith("34") || - cardNumber.startsWith("37")) { + cardNumber.startsWith("37") + ) { return "americanExpress" } else { return null From 1ff7b5d9889bb35b965983e2a51c8444817ceb4e Mon Sep 17 00:00:00 2001 From: 4f77616973 <0x4f@tuta.io> Date: Thu, 12 Jan 2023 19:28:51 +0530 Subject: [PATCH 03/46] fixed card pin bug in dashboard --- app/src/main/kotlin/cloud/keyspace/android/Dashboard.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/kotlin/cloud/keyspace/android/Dashboard.kt b/app/src/main/kotlin/cloud/keyspace/android/Dashboard.kt index 942c084..2af3ff0 100644 --- a/app/src/main/kotlin/cloud/keyspace/android/Dashboard.kt +++ b/app/src/main/kotlin/cloud/keyspace/android/Dashboard.kt @@ -2128,6 +2128,8 @@ class Dashboard : AppCompatActivity(), NavigationView.OnNavigationItemSelectedLi ) } + if (!card.pin.isNullOrBlank()) cardCard.pin.text = card.pin else cardCard.pinLayout.visibility = View.GONE + fun hideCodes () { cardCard.pin.text = "●●●●" cardCard.securityCode.text = "●●●" @@ -2138,6 +2140,7 @@ class Dashboard : AppCompatActivity(), NavigationView.OnNavigationItemSelectedLi var codesHidden = true hideCodes() + cardCard.hideCodes.setOnClickListener { codesHidden = !codesHidden if (codesHidden) { From 8054450e20fefcb5612b51fe14ded1a73cb89ab0 Mon Sep 17 00:00:00 2001 From: 4f77616973 <0x4f@tuta.io> Date: Thu, 12 Jan 2023 19:46:41 +0530 Subject: [PATCH 04/46] defaulted to username on login item if available --- app/src/main/kotlin/cloud/keyspace/android/Dashboard.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/cloud/keyspace/android/Dashboard.kt b/app/src/main/kotlin/cloud/keyspace/android/Dashboard.kt index 942c084..122aa7f 100644 --- a/app/src/main/kotlin/cloud/keyspace/android/Dashboard.kt +++ b/app/src/main/kotlin/cloud/keyspace/android/Dashboard.kt @@ -1086,7 +1086,9 @@ class Dashboard : AppCompatActivity(), NavigationView.OnNavigationItemSelectedLi if (!login.loginData?.email.isNullOrEmpty()) { loginCard.usernameText.text = login.loginData!!.email loginCard.usernameText.setCompoundDrawablesRelativeWithIntrinsicBounds (emailIcon, null, null, null) - } else if (!login.loginData?.username.isNullOrEmpty()) { + } else loginCard.usernameText.visibility = View.GONE + + if (!login.loginData?.username.isNullOrEmpty()) { loginCard.usernameText.text = login.loginData!!.username loginCard.usernameText.setCompoundDrawablesRelativeWithIntrinsicBounds (loginIcon, null, null, null) } else loginCard.usernameText.visibility = View.GONE From 720a37366c4961ad946afc4171b2f1eaaabe940b Mon Sep 17 00:00:00 2001 From: 4f77616973 <0x4f@tuta.io> Date: Thu, 12 Jan 2023 19:51:04 +0530 Subject: [PATCH 05/46] added username clearing to AddLogin.kt --- app/src/main/kotlin/cloud/keyspace/android/AddLogin.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/cloud/keyspace/android/AddLogin.kt b/app/src/main/kotlin/cloud/keyspace/android/AddLogin.kt index 6e1dba2..6dd0303 100644 --- a/app/src/main/kotlin/cloud/keyspace/android/AddLogin.kt +++ b/app/src/main/kotlin/cloud/keyspace/android/AddLogin.kt @@ -984,7 +984,7 @@ class AddLogin : AppCompatActivity() { favorite = favorite, tagId = tagPicker.getSelectedTagId() ?: tagId, loginData = IOUtilities.LoginData( - username = userNameInput.text.toString(), + username = if (!emailAsUsername.isChecked) userNameInput.text.toString() else null, password = passwordInput.text.toString(), passwordHistory = if (passwordHistoryData.size > 0) passwordHistoryData else null, email = emailInput.text.toString(), From 29f950394c00f173941dc6d7c81f38bc1f9a9266 Mon Sep 17 00:00:00 2001 From: 4f77616973 <0x4f@tuta.io> Date: Thu, 12 Jan 2023 19:53:21 +0530 Subject: [PATCH 06/46] added username clearing support logic to Dashboard --- app/src/main/kotlin/cloud/keyspace/android/Dashboard.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/cloud/keyspace/android/Dashboard.kt b/app/src/main/kotlin/cloud/keyspace/android/Dashboard.kt index 122aa7f..7d3625b 100644 --- a/app/src/main/kotlin/cloud/keyspace/android/Dashboard.kt +++ b/app/src/main/kotlin/cloud/keyspace/android/Dashboard.kt @@ -1086,12 +1086,14 @@ class Dashboard : AppCompatActivity(), NavigationView.OnNavigationItemSelectedLi if (!login.loginData?.email.isNullOrEmpty()) { loginCard.usernameText.text = login.loginData!!.email loginCard.usernameText.setCompoundDrawablesRelativeWithIntrinsicBounds (emailIcon, null, null, null) - } else loginCard.usernameText.visibility = View.GONE + } if (!login.loginData?.username.isNullOrEmpty()) { loginCard.usernameText.text = login.loginData!!.username loginCard.usernameText.setCompoundDrawablesRelativeWithIntrinsicBounds (loginIcon, null, null, null) - } else loginCard.usernameText.visibility = View.GONE + } + + if (login.loginData?.username.isNullOrEmpty() && login.loginData?.email.isNullOrEmpty()) loginCard.usernameText.visibility = View.GONE loginCard.miscText.visibility = View.GONE From 008b53a2a04a11c28787709936eaaf8568813eb8 Mon Sep 17 00:00:00 2001 From: 4f77616973 <0x4f@tuta.io> Date: Tue, 17 Jan 2023 01:59:53 +0530 Subject: [PATCH 07/46] added acra skeleton --- app/build.gradle | 10 +++-- app/proguard-rules.pro | 19 +++++++- app/src/main/AndroidManifest.xml | 1 + .../kotlin/cloud/keyspace/android/Keyspace.kt | 44 +++++++++++++++++++ .../cloud/keyspace/android/StartHere.kt | 3 +- app/src/main/res/values/strings.xml | 3 +- 6 files changed, 73 insertions(+), 7 deletions(-) create mode 100644 app/src/main/kotlin/cloud/keyspace/android/Keyspace.kt diff --git a/app/build.gradle b/app/build.gradle index 22699d1..668650b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -51,7 +51,7 @@ android { dependencies { implementation 'com.android.support:multidex:2.0.1' // noinspection GradleDependency implementation 'androidx.core:core-ktx:1.9.0' // Default Android stuff - implementation 'androidx.appcompat:appcompat:1.5.1' + implementation 'androidx.appcompat:appcompat:1.6.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.gridlayout:gridlayout:1.0.0' @@ -67,8 +67,8 @@ dependencies { implementation 'androidx.core:core-ktx:1.9.0' testImplementation 'junit:junit:4.13.2' // Testing implementation 'androidx.biometric:biometric:1.2.0-alpha05' // Biometrics - androidTestImplementation 'androidx.test.ext:junit:1.1.4' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' implementation 'com.google.android.material:material:1.7.0' // Material design implementation 'androidx.fragment:fragment-ktx:1.5.5' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4' @@ -99,4 +99,8 @@ dependencies { implementation 'com.scottyab:rootbeer-lib:0.1.0' // To check if device is rooted implementation "androidx.core:core-splashscreen:1.0.0" // Splash screen library implementation 'com.nulab-inc:zxcvbn:1.7.0' // password strength + + // ACRA for crash logging + implementation 'ch.acra:acra-mail:5.9.7' // mail component + implementation 'ch.acra:acra-toast:5.9.7' // dialog component } \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb43..63ab79d 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,4 +18,21 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile + +#ACRA specifics +# Restore some Source file names and restore approximate line numbers in the stack traces, +# otherwise the stack traces are pretty useless +-keepattributes SourceFile, LineNumberTable + +# ACRA needs "annotations" so add this... +# Note: This may already be defined in the default "proguard-android-optimize.txt" +# file in the SDK. If it is, then you don't need to duplicate it. See your +# "project.properties" file to get the path to the default "proguard-android-optimize.txt". +-keepattributes *Annotation* + +# Keep all the ACRA classes +-keep class org.acra.** { *; } + +# Don't warn about removed methods from AppCompat +-dontwarn android.support.v4.app.NotificationCompat* \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2239f5f..1014235 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,6 +20,7 @@ android:fullBackupContent="false" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" + android:name="cloud.keyspace.android.Keyspace" android:largeHeap="true" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" diff --git a/app/src/main/kotlin/cloud/keyspace/android/Keyspace.kt b/app/src/main/kotlin/cloud/keyspace/android/Keyspace.kt new file mode 100644 index 0000000..0672e2c --- /dev/null +++ b/app/src/main/kotlin/cloud/keyspace/android/Keyspace.kt @@ -0,0 +1,44 @@ +package cloud.keyspace.android + +import android.app.Application +import android.content.Context +import android.widget.Toast +import org.acra.config.mailSenderConfiguration +import org.acra.config.toastConfiguration +import org.acra.data.StringFormat +import org.acra.ktx.initAcra + +class Keyspace : Application() { + override fun attachBaseContext(base: Context) { + super.attachBaseContext(base) + + initAcra { + buildConfigClass = BuildConfig::class.java + reportFormat = StringFormat.JSON + + toastConfiguration { // Todo replace with dialog or notification + //required + text = getString(R.string.crash_send_logs_description) + //defaults to Toast.LENGTH_LONG + length = Toast.LENGTH_LONG + enabled = true + } + + mailSenderConfiguration { + //required + mailTo = "acra@yourserver.com" + //defaults to true + reportAsFile = true + //defaults to ACRA-report.stacktrace + reportFileName = "Crash.txt" + //defaults to " Crash Report" + subject = getString(R.string.crash_send_logs_description) + //defaults to empty + body = getString(R.string.crash_send_logs_description) + enabled = true + } + + } + + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/cloud/keyspace/android/StartHere.kt b/app/src/main/kotlin/cloud/keyspace/android/StartHere.kt index f3892f1..7c461fd 100644 --- a/app/src/main/kotlin/cloud/keyspace/android/StartHere.kt +++ b/app/src/main/kotlin/cloud/keyspace/android/StartHere.kt @@ -61,8 +61,6 @@ import kotlinx.coroutines.* import java.io.File import java.util.* import java.util.concurrent.Executor -import kotlin.system.exitProcess - private lateinit var _supportFragmentManager: FragmentManager @@ -1656,6 +1654,7 @@ class StartHere : AppCompatActivity() { override fun onAnimationStart(animation: Animation) { authenticateTitle.setText("All done!") authenticateDescription.setText("daaaammn if you can read this you have eagle eyes... \uD83E\uDD85") + throw ArrayIndexOutOfBoundsException() } override fun onAnimationRepeat(animation: Animation) { } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index acb455a..cbce790 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -33,7 +33,6 @@ Your Vault is kept secure using your device lock and is only decrypted when you unlock Keyspace. If your device has a fingerprint sensor or facial recognition, we recommend setting it up for your convenience.\n\nTap the button below to unlock. Your Vault is kept secure using your biometrics and is only decrypted when you unlock Keyspace.\n\nTap the button below to unlock. - Enter a passphrase Make sure it\'s strong Your Keyspace passphrase is an advanced security feature that lets you strengthen your account\'s security.\n\nThis step is optional. @@ -116,4 +115,6 @@ Verify that the words are in the exact order as given with proper spelling Store your recovery phrase in a secure location and make sure only you can access it + Keyspace just crashed. Would you like to email crash logs to the team? + \ No newline at end of file From 6e75b2f46a56a62739de7b2ca6d01052b625c937 Mon Sep 17 00:00:00 2001 From: 4f77616973 <0x4f@tuta.io> Date: Tue, 17 Jan 2023 04:02:25 +0530 Subject: [PATCH 08/46] added totp size checker --- app/src/main/kotlin/cloud/keyspace/android/AddLogin.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/cloud/keyspace/android/AddLogin.kt b/app/src/main/kotlin/cloud/keyspace/android/AddLogin.kt index 6e1dba2..1b32a80 100644 --- a/app/src/main/kotlin/cloud/keyspace/android/AddLogin.kt +++ b/app/src/main/kotlin/cloud/keyspace/android/AddLogin.kt @@ -195,6 +195,11 @@ class AddLogin : AppCompatActivity() { } } + if (secretInput.text.toString().isNotBlank() && secretInput.text.toString().length < 6) { + secretInput.error = "Please enter a valid TOTP secret" + return@setOnClickListener + } + saveItem() } @@ -629,7 +634,7 @@ class AddLogin : AppCompatActivity() { override fun afterTextChanged (s: Editable) { } override fun beforeTextChanged (s: CharSequence, start: Int, count: Int, after: Int) { } override fun onTextChanged (s: CharSequence, start: Int, before: Int, count: Int) { - if (s.length >= 8) { + if (s.length >= 6) { try { otpCode = GoogleAuthenticator(base32secret = secretInput.text.toString()).generate() runOnUiThread { tokenPreview.text = otpCode!!.replace("...".toRegex(), "$0 ") } From 1a940c5535a42d258ae9efa597ebb22c88e0a92b Mon Sep 17 00:00:00 2001 From: 4f77616973 <0x4f@tuta.io> Date: Tue, 17 Jan 2023 16:49:24 +0530 Subject: [PATCH 09/46] implemented crash handler --- app/build.gradle | 2 +- .../kotlin/cloud/keyspace/android/Keyspace.kt | 38 +++++++++---------- app/src/main/res/values/strings.xml | 14 ++++++- 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 668650b..627e21f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -102,5 +102,5 @@ dependencies { // ACRA for crash logging implementation 'ch.acra:acra-mail:5.9.7' // mail component - implementation 'ch.acra:acra-toast:5.9.7' // dialog component + implementation 'ch.acra:acra-dialog:5.9.7' // dialog component } \ No newline at end of file diff --git a/app/src/main/kotlin/cloud/keyspace/android/Keyspace.kt b/app/src/main/kotlin/cloud/keyspace/android/Keyspace.kt index 0672e2c..55612d8 100644 --- a/app/src/main/kotlin/cloud/keyspace/android/Keyspace.kt +++ b/app/src/main/kotlin/cloud/keyspace/android/Keyspace.kt @@ -2,40 +2,36 @@ package cloud.keyspace.android import android.app.Application import android.content.Context -import android.widget.Toast -import org.acra.config.mailSenderConfiguration -import org.acra.config.toastConfiguration +import org.acra.config.dialog +import org.acra.config.mailSender import org.acra.data.StringFormat import org.acra.ktx.initAcra +import java.sql.Time +import java.time.Instant +import java.time.format.DateTimeFormatter class Keyspace : Application() { override fun attachBaseContext(base: Context) { super.attachBaseContext(base) - initAcra { buildConfigClass = BuildConfig::class.java - reportFormat = StringFormat.JSON + reportFormat = StringFormat.KEY_VALUE_LIST - toastConfiguration { // Todo replace with dialog or notification - //required + dialog { text = getString(R.string.crash_send_logs_description) - //defaults to Toast.LENGTH_LONG - length = Toast.LENGTH_LONG - enabled = true + title = " " + getString(R.string.app_name) + positiveButtonText = getString(R.string.crash_send_positive_button_text) + negativeButtonText = getString(R.string.exit) + resIcon = R.drawable.keyspace + resTheme = android.R.style.Theme_Material_Dialog } - mailSenderConfiguration { - //required - mailTo = "acra@yourserver.com" - //defaults to true + mailSender { + mailTo = getString(R.string.support_email) reportAsFile = true - //defaults to ACRA-report.stacktrace - reportFileName = "Crash.txt" - //defaults to " Crash Report" - subject = getString(R.string.crash_send_logs_description) - //defaults to empty - body = getString(R.string.crash_send_logs_description) - enabled = true + reportFileName = "bug_report_${DateTimeFormatter.ISO_INSTANT.format(Instant.now())}.txt" + subject = getString(R.string.crash_send_logs_email_subject) + body = getString(R.string.crash_send_logs_email_body) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cbce790..5b4d459 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -115,6 +115,18 @@ Verify that the words are in the exact order as given with proper spelling Store your recovery phrase in a secure location and make sure only you can access it - Keyspace just crashed. Would you like to email crash logs to the team? + Keyspace Android app crash + This email contains a bug report of a recent crash on Keyspace Android. + Keyspace + Send email + Uh oh, Keyspace just crashed. Would you like to send a bug report to the team?\n\n + + This bug report will include:\n\n + + ⋅ Device information (such model, Android version etc.)\n + ⋅ App-related crash logs and exceptions\n\n + + This is optional. + \ No newline at end of file From 4e4ece05c021e4b4dfaa52fc7f24495b3e6fc314 Mon Sep 17 00:00:00 2001 From: 4f77616973 <0x4f@tuta.io> Date: Tue, 17 Jan 2023 23:57:25 +0530 Subject: [PATCH 10/46] removed generic exception handlers --- .../cloud/keyspace/android/StartHere.kt | 150 ++++++++---------- 1 file changed, 62 insertions(+), 88 deletions(-) diff --git a/app/src/main/kotlin/cloud/keyspace/android/StartHere.kt b/app/src/main/kotlin/cloud/keyspace/android/StartHere.kt index 7c461fd..8e19bff 100644 --- a/app/src/main/kotlin/cloud/keyspace/android/StartHere.kt +++ b/app/src/main/kotlin/cloud/keyspace/android/StartHere.kt @@ -373,15 +373,10 @@ class StartHere : AppCompatActivity() { loadingScreenFragmentView = inflater.inflate(R.layout.loading_screen, container, false) loadContent() - try { - generateCryptoObjects() - // logger() - if (mode == MODE_CREATE_ACCOUNT) createAccount() - else if (mode == MODE_SIGN_IN) signIn() + generateCryptoObjects() - } catch (unknownError: Exception) { - showCryptographyErrorDialog() - } + if (mode == MODE_CREATE_ACCOUNT) createAccount() + else if (mode == MODE_SIGN_IN) signIn() return loadingScreenFragmentView @@ -437,49 +432,39 @@ class StartHere : AppCompatActivity() { delay (500) setKeygen() - try { - generateCryptoObjects() - - keygenToSend() - delay(500) - - CoroutineScope(Dispatchers.IO).launch { - kotlin.runCatching { - val vaultData = network.grabLatestVaultFromBackend (signedToken) - withContext(Dispatchers.Main) { // used to run synchronous Kotlin functions like `suspend fun foo()` - sendToReceive() - - io.writeVault(vaultData) - - delay(500) - receiveToKeystore() - storeToKeyring() - delay (1000) - - keystoreToTick() - delay (3000) - - startPermissions() - } - }.onFailure { - when (it) { - is NetworkUtilities.IncorrectCredentialsException -> { - withContext(Dispatchers.Main) { - showIncorrectCredentialsDialog() - } + generateCryptoObjects() + keygenToSend() + delay(500) + + CoroutineScope(Dispatchers.IO).launch { + kotlin.runCatching { + val vaultData = network.grabLatestVaultFromBackend (signedToken) + withContext(Dispatchers.Main) { // used to run synchronous Kotlin functions like `suspend fun foo()` + sendToReceive() + io.writeVault(vaultData) + delay(500) + receiveToKeystore() + storeToKeyring() + delay (1000) + keystoreToTick() + delay (3000) + startPermissions() + } + }.onFailure { + when (it) { + is NetworkUtilities.IncorrectCredentialsException -> { + withContext(Dispatchers.Main) { + showIncorrectCredentialsDialog() } - is NetworkError -> { - withContext(Dispatchers.Main) { - showNetworkErrorDialog() - } + } + is NetworkError -> { + withContext(Dispatchers.Main) { + showNetworkErrorDialog() } - else -> throw it } + else -> throw it } } - - } catch (unknownError: Exception) { - showCryptographyErrorDialog () } } @@ -497,54 +482,44 @@ class StartHere : AppCompatActivity() { delay (500) setKeygen() - try { - generateCryptoObjects() + generateCryptoObjects() + keygenToSend() - keygenToSend() - CoroutineScope(Dispatchers.IO).launch { - kotlin.runCatching { - val createAccountResponse: NetworkUtilities.SignupResponse = network.sendSignupRequest( - NetworkUtilities.SignupParameters( - email = email, - public_key = publicKey.toHexString(), - signed_token = signedToken - ) - )!! - - if (createAccountResponse.status != network.RESPONSE_SUCCESS) throw NetworkUtilities.AccountExistsException() else { - withContext(Dispatchers.Main) { // used to run synchronous Kotlin functions like `suspend fun foo()` - delay(1000) - - receiveToKeystore() - storeToKeyring() - delay (1000) - - keystoreToTick() - delay (3000) - - startPermissions() - } + CoroutineScope(Dispatchers.IO).launch { + kotlin.runCatching { + val createAccountResponse: NetworkUtilities.SignupResponse = network.sendSignupRequest( + NetworkUtilities.SignupParameters( + email = email, + public_key = publicKey.toHexString(), + signed_token = signedToken + ) + )!! + if (createAccountResponse.status != network.RESPONSE_SUCCESS) throw NetworkUtilities.AccountExistsException() else { + withContext(Dispatchers.Main) { // used to run synchronous Kotlin functions like `suspend fun foo()` + delay(1000) + receiveToKeystore() + storeToKeyring() + delay (1000) + keystoreToTick() + delay (3000) + startPermissions() } - - }.onFailure { - when (it) { - is NetworkUtilities.AccountExistsException -> { - withContext(Dispatchers.Main) { - showDuplicateAccountDialog() - } + } + }.onFailure { + when (it) { + is NetworkUtilities.AccountExistsException -> { + withContext(Dispatchers.Main) { + showDuplicateAccountDialog() } - is NetworkError -> { - withContext(Dispatchers.Main) { - showNetworkErrorDialog() - } + } + is NetworkError -> { + withContext(Dispatchers.Main) { + showNetworkErrorDialog() } - else -> throw it } + else -> throw it } } - - } catch (unknownError: Exception) { - showCryptographyErrorDialog () } } @@ -1654,7 +1629,6 @@ class StartHere : AppCompatActivity() { override fun onAnimationStart(animation: Animation) { authenticateTitle.setText("All done!") authenticateDescription.setText("daaaammn if you can read this you have eagle eyes... \uD83E\uDD85") - throw ArrayIndexOutOfBoundsException() } override fun onAnimationRepeat(animation: Animation) { } From 5bc31a5e3131fdce41e2e888e7f23239f8b9d9ae Mon Sep 17 00:00:00 2001 From: 4f77616973 <0x4f@tuta.io> Date: Tue, 17 Jan 2023 23:58:52 +0530 Subject: [PATCH 11/46] removed generic exception handlers --- app/src/main/kotlin/cloud/keyspace/android/Keyspace.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/kotlin/cloud/keyspace/android/Keyspace.kt b/app/src/main/kotlin/cloud/keyspace/android/Keyspace.kt index 55612d8..4eb840c 100644 --- a/app/src/main/kotlin/cloud/keyspace/android/Keyspace.kt +++ b/app/src/main/kotlin/cloud/keyspace/android/Keyspace.kt @@ -6,7 +6,6 @@ import org.acra.config.dialog import org.acra.config.mailSender import org.acra.data.StringFormat import org.acra.ktx.initAcra -import java.sql.Time import java.time.Instant import java.time.format.DateTimeFormatter From 6735af4c5a6f6868120c690f1456f9aa02e3e098 Mon Sep 17 00:00:00 2001 From: 4f77616973 <0x4f@tuta.io> Date: Wed, 18 Jan 2023 05:59:54 +0530 Subject: [PATCH 12/46] fixed more than 19 digits issue --- .../kotlin/cloud/keyspace/android/AddCard.kt | 25 +++++++++++++++---- app/src/main/res/layout/edit_card.xml | 2 +- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/cloud/keyspace/android/AddCard.kt b/app/src/main/kotlin/cloud/keyspace/android/AddCard.kt index 01abb24..ae76dd5 100644 --- a/app/src/main/kotlin/cloud/keyspace/android/AddCard.kt +++ b/app/src/main/kotlin/cloud/keyspace/android/AddCard.kt @@ -9,6 +9,7 @@ import android.os.Bundle import android.os.Handler import android.os.Looper import android.text.Editable +import android.text.SpannableStringBuilder import android.text.TextUtils import android.text.TextWatcher import android.text.method.PasswordTransformationMethod @@ -266,12 +267,24 @@ class AddCard : AppCompatActivity() { if (s.isNotEmpty() && s.length % 5 == 0) { val c = s[s.length - 1] if (space == c) s.delete(s.length - 1, s.length) - } - - if (s.isNotEmpty() && s.length % 5 == 0) { - val c = s[s.length - 1] if (Character.isDigit(c) && TextUtils.split(s.toString(), space.toString()).size <= 3) s.insert(s.length - 1, space.toString()) } + if (s.toString().replace(" ", "").length in 0..16) { + cardNumberInput.removeTextChangedListener(this) + cardNumberInput.setText(s.toString().replace(" ", "").replace("....".toRegex(), "$0 ")?.trim()) + cardNumberInput.addTextChangedListener(this) + cardNumberInput.setSelection(cardNumberInput.text.toString().length) + } + if (s.toString().replace(" ", "").length in 17..18) { + for (c in s) { + if (c == ' ') { + s.delete(s.indexOf(c), s.indexOf(c)+1) + } + } + } + if (s.toString().replace(" ", "").length > 19) { + s.delete(s.length - 1, s.length) + } } override fun beforeTextChanged(cardNumber: CharSequence, start: Int, count: Int, after: Int) { } override fun onTextChanged(cardNumber: CharSequence, start: Int, before: Int, count: Int) { @@ -440,7 +453,9 @@ class AddCard : AppCompatActivity() { notesInput.setText(card.notes) - cardNumberInput.setText(card.cardNumber?.replace("....".toRegex(), "$0 ")) + if (card.cardNumber?.length!! == 16) cardNumberInput.setText(card.cardNumber.replace("....".toRegex(), "$0 ")) + else cardNumberInput.setText(card.cardNumber) + toDate.setText(card.expiry) securityCode.setText(card.securityCode) cardholderNameInput.setText(card.cardholderName) diff --git a/app/src/main/res/layout/edit_card.xml b/app/src/main/res/layout/edit_card.xml index 1444660..c0d99ad 100644 --- a/app/src/main/res/layout/edit_card.xml +++ b/app/src/main/res/layout/edit_card.xml @@ -211,7 +211,7 @@ android:digits="0123456789 " android:inputType="phone" android:letterSpacing="0.05" - android:maxLength="23" /> + android:maxLength="22" /> From a900727b7b4ad2bc3b42a59540359e3f4707e87a Mon Sep 17 00:00:00 2001 From: 4f77616973 <0x4f@tuta.io> Date: Wed, 18 Jan 2023 12:56:21 +0530 Subject: [PATCH 13/46] fixed dashboard crash --- app/src/main/kotlin/cloud/keyspace/android/Dashboard.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/cloud/keyspace/android/Dashboard.kt b/app/src/main/kotlin/cloud/keyspace/android/Dashboard.kt index 2af3ff0..d62c2ba 100644 --- a/app/src/main/kotlin/cloud/keyspace/android/Dashboard.kt +++ b/app/src/main/kotlin/cloud/keyspace/android/Dashboard.kt @@ -1966,7 +1966,9 @@ class Dashboard : AppCompatActivity(), NavigationView.OnNavigationItemSelectedLi } } - cardCard.cardNumber.text = card.cardNumber?.replace("....".toRegex(), "$0 ") + if (card.cardNumber?.length == 16) cardCard.cardNumber.text = card.cardNumber.replace("....".toRegex(), "$0 ") + else cardCard.cardNumber.text = card.cardNumber + cardCard.toDate.text = card.expiry cardCard.cardHolder.text = card.cardholderName From 9bb915ff3293ef6d599adb5d8530affd8de4c31a Mon Sep 17 00:00:00 2001 From: 4f77616973 <0x4f@tuta.io> Date: Thu, 19 Jan 2023 22:30:17 +0530 Subject: [PATCH 14/46] removed email/username switch --- .../kotlin/cloud/keyspace/android/AddLogin.kt | 16 +--------------- app/src/main/res/layout/edit_login.xml | 9 --------- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/app/src/main/kotlin/cloud/keyspace/android/AddLogin.kt b/app/src/main/kotlin/cloud/keyspace/android/AddLogin.kt index 6dd0303..0dc1929 100644 --- a/app/src/main/kotlin/cloud/keyspace/android/AddLogin.kt +++ b/app/src/main/kotlin/cloud/keyspace/android/AddLogin.kt @@ -74,8 +74,6 @@ class AddLogin : AppCompatActivity() { lateinit var emailInputLayout: TextInputLayout lateinit var emailInput: TextInputEditText - lateinit var emailAsUsername: MaterialSwitch - lateinit var passwordInputLayout: TextInputLayout lateinit var passwordInput: TextInputEditText lateinit var clearButton: ImageView @@ -290,14 +288,6 @@ class AddLogin : AppCompatActivity() { userNameInput = findViewById (R.id.userNameInput) userNameInput.imeOptions = IME_FLAG_NO_PERSONALIZED_LEARNING userNameInputLayout = findViewById (R.id.userNameInputLayout) - userNameInputLayout.visibility = View.GONE - - emailAsUsername = findViewById (R.id.emailAsUsername) - emailAsUsername.isChecked = true - emailAsUsername.setOnCheckedChangeListener { _, isChecked -> - if (isChecked) userNameInputLayout.visibility = View.GONE - else userNameInputLayout.visibility = View.VISIBLE - } emailInput = findViewById (R.id.emailInput) emailInput.imeOptions = IME_FLAG_NO_PERSONALIZED_LEARNING @@ -784,10 +774,6 @@ class AddLogin : AppCompatActivity() { if (!login.loginData!!.username.isNullOrBlank()) { userNameInput.setText(login.loginData.username) - emailAsUsername.isChecked = false - } else { - userNameInputLayout.visibility = View.GONE - emailAsUsername.isChecked = true } if (!login.loginData.password.isNullOrEmpty()) { @@ -984,7 +970,7 @@ class AddLogin : AppCompatActivity() { favorite = favorite, tagId = tagPicker.getSelectedTagId() ?: tagId, loginData = IOUtilities.LoginData( - username = if (!emailAsUsername.isChecked) userNameInput.text.toString() else null, + username = userNameInput.text.toString(), password = passwordInput.text.toString(), passwordHistory = if (passwordHistoryData.size > 0) passwordHistoryData else null, email = emailInput.text.toString(), diff --git a/app/src/main/res/layout/edit_login.xml b/app/src/main/res/layout/edit_login.xml index a0f2ae8..5b0ee09 100644 --- a/app/src/main/res/layout/edit_login.xml +++ b/app/src/main/res/layout/edit_login.xml @@ -216,15 +216,6 @@ android:inputType="textFilter" /> - - Date: Thu, 19 Jan 2023 22:58:44 +0530 Subject: [PATCH 15/46] added an email subject prefix --- app/src/main/kotlin/cloud/keyspace/android/Keyspace.kt | 2 +- app/src/main/res/values/strings.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/cloud/keyspace/android/Keyspace.kt b/app/src/main/kotlin/cloud/keyspace/android/Keyspace.kt index 4eb840c..c4d7fdd 100644 --- a/app/src/main/kotlin/cloud/keyspace/android/Keyspace.kt +++ b/app/src/main/kotlin/cloud/keyspace/android/Keyspace.kt @@ -28,7 +28,7 @@ class Keyspace : Application() { mailSender { mailTo = getString(R.string.support_email) reportAsFile = true - reportFileName = "bug_report_${DateTimeFormatter.ISO_INSTANT.format(Instant.now())}.txt" + reportFileName = "android_bug_report_${DateTimeFormatter.ISO_INSTANT.format(Instant.now())}.txt" subject = getString(R.string.crash_send_logs_email_subject) body = getString(R.string.crash_send_logs_email_body) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5b4d459..7655ac2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -115,8 +115,8 @@ Verify that the words are in the exact order as given with proper spelling Store your recovery phrase in a secure location and make sure only you can access it - Keyspace Android app crash - This email contains a bug report of a recent crash on Keyspace Android. + [KEYSPACE-ANDROID-CRASH] Keyspace Android crashes on my device + This email contains a bug report of a recent crash I encountered on Keyspace Android. Keyspace Send email Uh oh, Keyspace just crashed. Would you like to send a bug report to the team?\n\n From 18f4b2024fa4e8c7dc9502b5e07fd06fc81d8426 Mon Sep 17 00:00:00 2001 From: 4f77616973 <0x4f@tuta.io> Date: Sun, 29 Jan 2023 22:30:06 +0530 Subject: [PATCH 16/46] added basic deleted items UI --- app/src/main/AndroidManifest.xml | 6 + .../cloud/keyspace/android/Dashboard.kt | 12 ++ .../cloud/keyspace/android/DeletedItems.kt | 40 ++++++ app/src/main/res/layout/deleted_items.xml | 122 ++++++++++++++++++ .../main/res/layout/keyspace_account_info.xml | 14 +- 5 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 app/src/main/kotlin/cloud/keyspace/android/DeletedItems.kt create mode 100644 app/src/main/res/layout/deleted_items.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2239f5f..39465b3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -73,6 +73,12 @@ android:theme="@style/Theme.KeyspaceMobile.NoActionBar" android:configChanges="keyboardHidden|orientation|screenSize" android:windowSoftInputMode="adjustPan" /> + (R.id.signOutButton) val syncButton = loginInfoBox.findViewById(R.id.syncButton) + val deleteButton = loginInfoBox.findViewById(R.id.deleteButton) val sendFeedbackButton = loginInfoBox.findViewById(R.id.sendFeedbackButton) val keyspaceLogoHeader = loginInfoBox.findViewById(R.id.keyspaceLogoHeader) val settingsButton = loginInfoBox.findViewById(R.id.settingsButton) @@ -2848,6 +2855,11 @@ class Dashboard : AppCompatActivity(), NavigationView.OnNavigationItemSelectedLi val privacyPolicyButton = loginInfoBox.findViewById(R.id.privacyPolicyButton) val termsOfServiceButton = loginInfoBox.findViewById(R.id.termsOfServiceButton) + deleteButton.setOnClickListener { + dialog.dismiss() + openDeletedItems() + } + syncButton.setOnClickListener { dialog.dismiss() Toast.makeText(applicationContext, "Syncing vault", Toast.LENGTH_SHORT).show() diff --git a/app/src/main/kotlin/cloud/keyspace/android/DeletedItems.kt b/app/src/main/kotlin/cloud/keyspace/android/DeletedItems.kt new file mode 100644 index 0000000..2a318af --- /dev/null +++ b/app/src/main/kotlin/cloud/keyspace/android/DeletedItems.kt @@ -0,0 +1,40 @@ +package cloud.keyspace.android + +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Bundle +import android.provider.Settings +import android.text.Editable +import android.text.TextWatcher +import android.view.View +import android.view.animation.AnimationUtils +import android.widget.* +import androidx.appcompat.app.AppCompatActivity +import com.google.android.material.button.MaterialButton +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.material.materialswitch.MaterialSwitch +import com.google.android.material.radiobutton.MaterialRadioButton +import com.google.android.material.textfield.TextInputEditText +import com.yahiaangelo.markdownedittext.MarkdownEditText +import java.util.* + + +class DeletedItems : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.deleted_items) + + val configData = getSharedPreferences (applicationContext.packageName + "_configuration_data", MODE_PRIVATE) + + + + } + + override fun onBackPressed() { + super.onBackPressed() + val intent = Intent(this, StartHere::class.java) + this.startActivity(intent) + finishAffinity() + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/deleted_items.xml b/app/src/main/res/layout/deleted_items.xml new file mode 100644 index 0000000..4ddc989 --- /dev/null +++ b/app/src/main/res/layout/deleted_items.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/keyspace_account_info.xml b/app/src/main/res/layout/keyspace_account_info.xml index e8f0e58..3001338 100644 --- a/app/src/main/res/layout/keyspace_account_info.xml +++ b/app/src/main/res/layout/keyspace_account_info.xml @@ -190,7 +190,7 @@ android:layout_height="wrap_content" android:layout_weight="1" android:paddingBottom="2.5dp" - android:text="Will upload 2 items when online" + android:text="Will upload n items when online" android:textSize="12sp" /> + + Date: Sun, 29 Jan 2023 23:56:33 +0530 Subject: [PATCH 17/46] made settings UI match Android 13 spec even more --- .../kotlin/cloud/keyspace/android/Settings.kt | 9 +- app/src/main/res/layout/settings.xml | 97 +++++++++++-------- 2 files changed, 61 insertions(+), 45 deletions(-) diff --git a/app/src/main/kotlin/cloud/keyspace/android/Settings.kt b/app/src/main/kotlin/cloud/keyspace/android/Settings.kt index edc97fd..3f192a3 100644 --- a/app/src/main/kotlin/cloud/keyspace/android/Settings.kt +++ b/app/src/main/kotlin/cloud/keyspace/android/Settings.kt @@ -27,6 +27,9 @@ class Settings : AppCompatActivity() { val configData = getSharedPreferences (applicationContext.packageName + "_configuration_data", MODE_PRIVATE) + val backButton: ImageView = findViewById(R.id.backButton) + backButton.setOnClickListener { onBackPressed() } + val keyspaceAccountPicture: ImageView = findViewById(R.id.keyspaceAccountPicture) keyspaceAccountPicture.setImageDrawable(MiscUtilities(applicationContext).generateProfilePicture(configData.getString("userEmail", null)!!)) @@ -59,14 +62,14 @@ class Settings : AppCompatActivity() { val strongBoxIcon: ImageView = findViewById(R.id.strongBoxTypeIcon) if (applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE)) { // Check if strongbox / hardware keystore exists - strongBoxText.text = "Keyspace is encrypting your keys and tokens using tamper-resistant Strongbox hardware on your phone." + strongBoxText.text = "Keys are encrypted using tamper-resistant Strongbox hardware." strongBoxIcon.setImageDrawable(getDrawable(R.drawable.ic_baseline_chip_24)) } else { if (applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_HARDWARE_KEYSTORE)) { - strongBoxText.text = "Your phone doesn't contain Strongbox hardware. Using Hardware Abstraction Layer (HAL) based Keystore." + strongBoxText.text = "Keys are encrypted using Hardware Abstraction Layer (HAL) Keystore." strongBoxIcon.setImageDrawable(getDrawable(R.drawable.ic_baseline_code_24)) } else { - strongBoxText.text = "Your phone doesn't contain strongbox hardware. Using container-based Keystore." + strongBoxText.text = "Keys are encrypted using container-based Keystore." strongBoxIcon.setImageDrawable(getDrawable(R.drawable.ic_baseline_insert_drive_file_24)) } } diff --git a/app/src/main/res/layout/settings.xml b/app/src/main/res/layout/settings.xml index 9ed4cd6..85d8ebf 100644 --- a/app/src/main/res/layout/settings.xml +++ b/app/src/main/res/layout/settings.xml @@ -20,12 +20,21 @@ android:layout_height="match_parent" android:paddingBottom="50dp" android:orientation="vertical" - android:paddingTop="55dp"> + android:paddingTop="15dp" > + + + android:textSize="35sp" />