Compare commits

...
Author SHA1 Message Date
shihaam dd620763ec new feature: add launcher shortcuts
Auto Tag on Version Change / check-version (push) Successful in 5s
2026-05-28 14:06:49 +05:00
shihaam 86063d600f Fix Bug that allowed lockscreen bypass on rooted androids
Auto Tag on Version Change / check-version (push) Successful in 7s
2026-05-28 13:41:39 +05:00
17 changed files with 220 additions and 7 deletions
+4 -1
View File
@@ -10,7 +10,7 @@
<application
android:name=".BasedBankApp"
android:allowBackup="true"
android:allowBackup="false"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
@@ -30,6 +30,9 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity>
<activity
@@ -16,6 +16,13 @@ import sh.sar.basedbank.util.CredentialStore
class BasedBankApp : Application() {
/**
* Set to true only after the user passes LockActivity or completes fresh login.
* Resets to false on every process restart so direct ADB/root activity launches
* cannot reach HomeActivity without re-authenticating.
*/
var isUnlocked = false
// Held in memory after successful login; cleared on logout
var accounts: List<BankAccount> = emptyList()
var fullName: String = ""
@@ -21,6 +21,7 @@ import kotlinx.coroutines.withContext
import sh.sar.basedbank.databinding.ActivityLockBinding
import sh.sar.basedbank.ui.home.HomeActivity
import sh.sar.basedbank.util.CredentialStore
import sh.sar.basedbank.BasedBankApp
import javax.crypto.SecretKeyFactory
import javax.crypto.spec.PBEKeySpec
@@ -259,10 +260,16 @@ class LockActivity : AppCompatActivity() {
}
private fun proceed() {
(application as BasedBankApp).isUnlocked = true
if (intent.getBooleanExtra(EXTRA_RESUME, false)) {
finish()
} else {
startActivity(Intent(this, HomeActivity::class.java))
val navDest = intent.getIntExtra("nav_destination", -1)
val autoScan = intent.getBooleanExtra("auto_scan", false)
startActivity(Intent(this, HomeActivity::class.java).apply {
if (navDest != -1) putExtra("nav_destination", navDest)
if (autoScan) putExtra("auto_scan", true)
})
finish()
}
}
@@ -7,24 +7,43 @@ import sh.sar.basedbank.ui.home.HomeActivity
import sh.sar.basedbank.ui.login.LoginActivity
import sh.sar.basedbank.ui.onboarding.OnboardingActivity
import sh.sar.basedbank.util.CredentialStore
import sh.sar.basedbank.BasedBankApp
import sh.sar.basedbank.R
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val prefs = getSharedPreferences("prefs", MODE_PRIVATE)
val onboardingDone = prefs.getBoolean("onboarding_done", false)
val securitySet = prefs.getString("security_method", null) != null
val store = CredentialStore(this)
val hasCredentials = store.hasMibCredentials() || store.hasBmlCredentials() || store.hasFahipayCredentials()
val navDestination = when (intent?.action) {
"sh.sar.basedbank.OPEN_TRANSFER" -> R.id.nav_transfer
"sh.sar.basedbank.OPEN_SCAN_QR" -> R.id.nav_transfer
"sh.sar.basedbank.OPEN_PAY_WITH_CARD" -> R.id.nav_pay_with_card
else -> -1
}
val autoScan = intent?.action == "sh.sar.basedbank.OPEN_SCAN_QR"
val target = when {
!onboardingDone -> OnboardingActivity::class.java
!hasCredentials -> LoginActivity::class.java
securitySet -> LockActivity::class.java // proceed() → HomeActivity
else -> HomeActivity::class.java
}
startActivity(Intent(this, target))
// No lock screen configured — mark as unlocked so HomeActivity's guard passes
if (target == HomeActivity::class.java) {
(application as BasedBankApp).isUnlocked = true
}
startActivity(Intent(this, target).apply {
if (navDestination != -1) putExtra("nav_destination", navDestination)
if (autoScan) putExtra("auto_scan", true)
})
finish()
}
}
@@ -118,6 +118,18 @@ class HomeActivity : AppCompatActivity() {
isAppearanceLightStatusBars = isLight
isAppearanceLightNavigationBars = isLight
}
// Auth guard: HomeActivity must only be reachable after LockActivity or fresh login.
// Using loadSecurityHash() (EncryptedSharedPreferences) instead of plain prefs so
// a rooted device cannot bypass this by editing security_method in plain prefs.
val app = application as BasedBankApp
if (CredentialStore(this).loadSecurityHash() != null && !app.isUnlocked) {
startActivity(
android.content.Intent(this, sh.sar.basedbank.LockActivity::class.java)
)
finish()
return
}
setSupportActionBar(binding.toolbar)
toggle = ActionBarDrawerToggle(
@@ -165,7 +177,6 @@ class HomeActivity : AppCompatActivity() {
}
// Load data
val app = application as BasedBankApp
if (app.mibAccounts.isNotEmpty() || app.bmlAccounts.isNotEmpty() || app.fahipayAccounts.isNotEmpty()) {
// Came from fresh manual login — accounts ready, rest fetched in background
val merged = app.mibAccounts + app.bmlAccounts + app.fahipayAccounts
@@ -218,10 +229,19 @@ class HomeActivity : AppCompatActivity() {
viewModel.hideAmounts.value = getSharedPreferences("prefs", MODE_PRIVATE).getBoolean("hide_amounts", false)
// Show dashboard on first create
// Show dashboard on first create, or navigate to shortcut destination
if (savedInstanceState == null) {
show(DashboardFragment())
binding.navigationView.setCheckedItem(R.id.nav_dashboard)
val navDest = intent.getIntExtra("nav_destination", -1)
val autoScan = intent.getBooleanExtra("auto_scan", false)
if (navDest != -1) {
val fragment = if (autoScan && navDest == R.id.nav_transfer)
TransferFragment.newInstanceWithAutoScan()
else null
navigateTo(navDest, fragment)
} else {
show(DashboardFragment())
binding.navigationView.setCheckedItem(R.id.nav_dashboard)
}
}
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
@@ -37,8 +37,10 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import sh.sar.basedbank.BasedBankApp
import sh.sar.basedbank.R
import sh.sar.basedbank.databinding.ActivityQrScannerBinding
import sh.sar.basedbank.util.CredentialStore
import java.util.concurrent.Executors
class QrScannerActivity : AppCompatActivity() {
@@ -95,6 +97,14 @@ class QrScannerActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (CredentialStore(this).loadSecurityHash() != null &&
!(application as BasedBankApp).isUnlocked) {
startActivity(Intent(this, sh.sar.basedbank.LockActivity::class.java))
finish()
return
}
WindowCompat.setDecorFitsSystemWindows(window, false)
binding = ActivityQrScannerBinding.inflate(layoutInflater)
setContentView(binding.root)
@@ -151,6 +151,11 @@ class TransferFragment : Fragment() {
private const val ARG_AMOUNT_PREFILL = "amount_prefill"
private const val ARG_REMARKS_PREFILL = "remarks_prefill"
private const val ARG_BML_QR_URL = "bml_qr_url"
private const val ARG_AUTO_SCAN = "auto_scan"
fun newInstanceWithAutoScan() = TransferFragment().apply {
arguments = Bundle().apply { putBoolean(ARG_AUTO_SCAN, true) }
}
fun newInstanceFromBmlQr(qrUrl: String, fromAccountNumber: String? = null) = TransferFragment().apply {
arguments = Bundle().apply {
@@ -253,6 +258,10 @@ class TransferFragment : Fragment() {
arguments?.getString(ARG_REMARKS_PREFILL)?.let { binding.etRemarks.setText(it) }
arguments?.getString(ARG_BML_QR_URL)?.let { lookupBmlQrMerchant(it) }
if (arguments?.getBoolean(ARG_AUTO_SCAN, false) == true) {
qrLauncher.launch(Intent(requireContext(), QrScannerActivity::class.java))
}
}
private fun lookupBmlQrMerchant(qrUrl: String) {
@@ -215,6 +215,7 @@ class CredentialsFragment : Fragment() {
app.mibSessions[loginId] = flow.lastSession!!
app.mibProfilesMap[loginId] = flow.lastProfiles
app.mibLoginFlows[loginId] = flow
app.isUnlocked = true
val intent = Intent(requireContext(), HomeActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(intent)
@@ -364,6 +365,7 @@ class CredentialsFragment : Fragment() {
if (hasBusinessProfiles) {
Toast.makeText(requireContext(), "Business profiles can be enabled in Settings → Logins", Toast.LENGTH_LONG).show()
}
(requireActivity().application as BasedBankApp).isUnlocked = true
val intent = Intent(requireContext(), HomeActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(intent)
@@ -496,6 +498,7 @@ class CredentialsFragment : Fragment() {
app.fahipaySessions[loginId] = session
app.fahipayAccounts = app.fahipayAccounts.filter { it.loginTag != loginTag } + listOf(account)
app.accounts = app.accounts.filter { it.loginTag != loginTag } + listOf(account)
app.isUnlocked = true
val intent = Intent(requireContext(), HomeActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(intent)
@@ -1,10 +1,14 @@
package sh.sar.basedbank.ui.login
import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import sh.sar.basedbank.BasedBankApp
import sh.sar.basedbank.LockActivity
import sh.sar.basedbank.databinding.ActivityLoginBinding
import sh.sar.basedbank.util.CredentialStore
class LoginActivity : AppCompatActivity() {
@@ -12,6 +16,16 @@ class LoginActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// If security is configured and the user hasn't unlocked this session,
// they must authenticate first before being able to add more accounts.
val app = application as BasedBankApp
if (CredentialStore(this).loadSecurityHash() != null && !app.isUnlocked) {
startActivity(Intent(this, LockActivity::class.java))
finish()
return
}
WindowCompat.setDecorFitsSystemWindows(window, false)
binding = ActivityLoginBinding.inflate(layoutInflater)
setContentView(binding.root)
@@ -13,9 +13,12 @@ import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.tabs.TabLayoutMediator
import sh.sar.basedbank.BasedBankApp
import sh.sar.basedbank.LockActivity
import sh.sar.basedbank.R
import sh.sar.basedbank.databinding.ActivityOnboardingBinding
import sh.sar.basedbank.ui.login.LoginActivity
import sh.sar.basedbank.util.CredentialStore
class OnboardingActivity : AppCompatActivity(), SecuritySetupFragment.Callback {
@@ -25,6 +28,15 @@ class OnboardingActivity : AppCompatActivity(), SecuritySetupFragment.Callback {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// If security is already configured, onboarding is complete. Redirect to lock screen
// to prevent overwriting an existing PIN/pattern via direct activity launch.
if (CredentialStore(this).loadSecurityHash() != null) {
startActivity(Intent(this, LockActivity::class.java))
finish()
return
}
WindowCompat.setDecorFitsSystemWindows(window, false)
binding = ActivityOnboardingBinding.inflate(layoutInflater)
setContentView(binding.root)
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/seed_primary" />
<foreground android:drawable="@drawable/ic_shortcut_pay_card_fg" />
<monochrome android:drawable="@drawable/ic_shortcut_pay_card_fg" />
</adaptive-icon>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group
android:translateX="30"
android:translateY="30"
android:scaleX="2"
android:scaleY="2">
<path
android:fillColor="#FFFFFF"
android:pathData="M20,4H4C2.89,4 2.01,4.89 2.01,6L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2V6C22,4.89 21.11,4 20,4zM20,18H4v-6h16V18zM20,8H4V6h16V8z" />
</group>
</vector>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/seed_primary" />
<foreground android:drawable="@drawable/ic_shortcut_scan_qr_fg" />
<monochrome android:drawable="@drawable/ic_shortcut_scan_qr_fg" />
</adaptive-icon>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group
android:translateX="30"
android:translateY="30"
android:scaleX="2"
android:scaleY="2">
<path
android:fillColor="#FFFFFF"
android:pathData="M9.5,6.5v3h-3v-3H9.5zM11,5L5,5v5.5h6L11,5zM9.5,14.5v3h-3v-3H9.5zM11,13L5,13v5.5h6L11,13zM17.5,6.5v3h-3v-3H17.5zM19,5h-6v5.5h6L19,5zM13,13h1.5v1.5L13,14.5zM14.5,14.5L16,14.5L16,16h-1.5zM16,13h1.5v1.5L16,14.5zM13,16h1.5v1.5L13,17.5zM14.5,17.5L16,17.5L16,19h-1.5zM16,16h1.5v1.5L16,17.5zM17.5,14.5L19,14.5L19,16h-1.5zM17.5,17.5L19,17.5L19,19h-1.5z" />
</group>
</vector>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/seed_primary" />
<foreground android:drawable="@drawable/ic_shortcut_transfer_fg" />
<monochrome android:drawable="@drawable/ic_shortcut_transfer_fg" />
</adaptive-icon>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group
android:translateX="30"
android:translateY="30"
android:scaleX="2"
android:scaleY="2">
<path
android:fillColor="#FFFFFF"
android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z" />
</group>
</vector>
+43
View File
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut
android:shortcutId="transfer"
android:enabled="true"
android:icon="@drawable/ic_shortcut_transfer"
android:shortcutShortLabel="@string/transfer"
android:shortcutLongLabel="@string/transfer">
<intent
android:action="sh.sar.basedbank.OPEN_TRANSFER"
android:targetPackage="sh.sar.basedbank"
android:targetClass="sh.sar.basedbank.MainActivity" />
<categories android:name="android.shortcut.conversation" />
</shortcut>
<shortcut
android:shortcutId="scan_qr"
android:enabled="true"
android:icon="@drawable/ic_shortcut_scan_qr"
android:shortcutShortLabel="@string/transfer_scan_qr"
android:shortcutLongLabel="@string/transfer_scan_qr">
<intent
android:action="sh.sar.basedbank.OPEN_SCAN_QR"
android:targetPackage="sh.sar.basedbank"
android:targetClass="sh.sar.basedbank.MainActivity" />
<categories android:name="android.shortcut.conversation" />
</shortcut>
<shortcut
android:shortcutId="pay_with_card"
android:enabled="true"
android:icon="@drawable/ic_shortcut_pay_card"
android:shortcutShortLabel="@string/nav_pay_with_card"
android:shortcutLongLabel="@string/nav_pay_with_card">
<intent
android:action="sh.sar.basedbank.OPEN_PAY_WITH_CARD"
android:targetPackage="sh.sar.basedbank"
android:targetClass="sh.sar.basedbank.MainActivity" />
<categories android:name="android.shortcut.conversation" />
</shortcut>
</shortcuts>