Skip to content

Commit

Permalink
Implement global app locking
Browse files Browse the repository at this point in the history
 - Added a full screen activity for unlocking app
 - Added app lock manager for managing global lock state
 - Added new FossifyApp base application class that apps must subclass.

This will help remedy issues like FossifyOrg/Gallery#126
  • Loading branch information
naveensingh committed Sep 15, 2024
1 parent c144de7 commit 1e5b935
Show file tree
Hide file tree
Showing 33 changed files with 544 additions and 208 deletions.
2 changes: 1 addition & 1 deletion commons/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ dependencies {
implementation(libs.androidx.swiperefreshlayout)
implementation(libs.androidx.exifinterface)
implementation(libs.androidx.biometric.ktx)
implementation(libs.androidx.lifecycle.process)
implementation(libs.ez.vcard)


implementation(libs.bundles.lifecycle)
implementation(libs.bundles.compose)
implementation(libs.compose.view.binding)
Expand Down
4 changes: 4 additions & 0 deletions commons/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@
android:exported="false"
android:label="@string/donate_to_fossify" />

<activity
android:name=".activities.AppLockActivity"
android:exported="false" />

<receiver
android:name=".receivers.SharedThemeReceiver"
android:enabled="true"
Expand Down
19 changes: 19 additions & 0 deletions commons/src/main/kotlin/org/fossify/commons/FossifyApp.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.fossify.commons

import android.app.Application
import androidx.lifecycle.ProcessLifecycleOwner
import org.fossify.commons.extensions.appLockManager
import org.fossify.commons.extensions.checkUseEnglish

open class FossifyApp : Application() {

override fun onCreate() {
super.onCreate()
checkUseEnglish()
setupAppLockManager()
}

private fun setupAppLockManager() {
ProcessLifecycleOwner.get().lifecycle.addObserver(appLockManager)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
Expand All @@ -26,7 +25,7 @@ import org.fossify.commons.extensions.*
import org.fossify.commons.helpers.*
import org.fossify.commons.models.FAQItem

class AboutActivity : ComponentActivity() {
class AboutActivity : BaseComposeActivity() {
private val appName get() = intent.getStringExtra(APP_NAME) ?: ""

private var firstVersionClickTS = 0L
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package org.fossify.commons.activities

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import androidx.activity.addCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.biometric.auth.AuthPromptHost
import org.fossify.commons.R
import org.fossify.commons.adapters.AppLockAdapter
import org.fossify.commons.databinding.ActivityAppLockBinding
import org.fossify.commons.extensions.*
import org.fossify.commons.helpers.PROTECTION_FINGERPRINT
import org.fossify.commons.helpers.isRPlus
import org.fossify.commons.interfaces.HashListener

class AppLockActivity : AppCompatActivity(), HashListener {

private val binding by viewBinding(ActivityAppLockBinding::inflate)

override fun onCreate(savedInstanceState: Bundle?) {
overrideActivityTransition()
setupTheme()

super.onCreate(savedInstanceState)
setContentView(binding.root)
onBackPressedDispatcher.addCallback(owner = this) {
appLockManager.lock()
finishAffinity()
}

setupViewPager()
}

private fun setupViewPager() {
val adapter = AppLockAdapter(
context = binding.root.context,
requiredHash = baseConfig.appPasswordHash,
hashListener = this,
viewPager = binding.viewPager,
biometricPromptHost = AuthPromptHost(this),
showBiometricIdTab = isBiometricAuthSupported(),
showBiometricAuthentication = baseConfig.appProtectionType == PROTECTION_FINGERPRINT && isRPlus()
)

binding.viewPager.apply {
this.adapter = adapter
currentItem = baseConfig.appProtectionType
isUserInputEnabled = false
onGlobalLayout {
for (i in 0..2) {
adapter.isTabVisible(i, binding.viewPager.currentItem == i)
}
}
}
}

override fun onResume() {
super.onResume()
if (appLockManager.isLocked()) {
setupTheme()
} else {
finish()
}
}

override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
overrideActivityTransition()
}

override fun finish() {
super.finish()
overrideActivityTransition(exiting = true)
}

private fun setupTheme() {
setTheme(getThemeId(showTransparentTop = true))
with(getProperBackgroundColor()) {
window.updateStatusBarColors(this)
window.updateNavigationBarColors(this)
window.decorView.setBackgroundColor(this)
}
}

private fun overrideActivityTransition(exiting: Boolean = false) {
overrideActivityTransition(R.anim.fadein, R.anim.fadeout, exiting)
}

override fun receivedHash(hash: String, type: Int) {
appLockManager.unlock()
setResult(RESULT_OK)
finish()
}
}

fun Activity.maybeLaunchAppUnlockActivity(requestCode: Int) {
if (appLockManager.isLocked()) {
Intent(this, AppLockActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
startActivityForResult(this, requestCode)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.fossify.commons.activities

import androidx.activity.ComponentActivity
import org.fossify.commons.helpers.REQUEST_APP_UNLOCK

abstract class BaseComposeActivity : ComponentActivity() {

override fun onResume() {
super.onResume()
maybeLaunchAppUnlockActivity(REQUEST_APP_UNLOCK)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ abstract class BaseSimpleActivity : AppCompatActivity() {
}

updateNavigationBarColor(navBarColor)
maybeLaunchAppUnlockActivity(requestCode = REQUEST_APP_UNLOCK)
}

override fun onDestroy() {
Expand Down Expand Up @@ -182,13 +183,7 @@ abstract class BaseSimpleActivity : AppCompatActivity() {
}

fun updateStatusbarColor(color: Int) {
window.statusBarColor = color

if (color.getContrastColor() == DARK_GREY) {
window.decorView.systemUiVisibility = window.decorView.systemUiVisibility.addBit(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR)
} else {
window.decorView.systemUiVisibility = window.decorView.systemUiVisibility.removeBit(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR)
}
window.updateStatusBarColors(color)
}

fun animateStatusBarColor(colorTo: Int, colorFrom: Int = window.statusBarColor, duration: Long = 300L) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package org.fossify.commons.activities

import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.remember
import kotlinx.collections.immutable.toImmutableList
import org.fossify.commons.R
Expand All @@ -11,7 +10,7 @@ import org.fossify.commons.compose.screens.ContributorsScreen
import org.fossify.commons.compose.theme.AppThemeSurface
import org.fossify.commons.models.LanguageContributor

class ContributorsActivity : AppCompatActivity() {
class ContributorsActivity : BaseComposeActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdgeSimple()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.fossify.commons.activities

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.text.AnnotatedString
Expand All @@ -14,7 +13,7 @@ import org.fossify.commons.compose.theme.AppThemeSurface
import org.fossify.commons.extensions.openWebsiteIntent
import org.fossify.commons.extensions.toast

class DonationActivity : ComponentActivity() {
class DonationActivity : BaseComposeActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.fossify.commons.activities

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.runtime.remember
import kotlinx.collections.immutable.toImmutableList
Expand All @@ -11,7 +10,7 @@ import org.fossify.commons.compose.theme.AppThemeSurface
import org.fossify.commons.helpers.APP_FAQ
import org.fossify.commons.models.FAQItem

class FAQActivity : ComponentActivity() {
class FAQActivity : BaseComposeActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdgeSimple()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.fossify.commons.activities

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
Expand All @@ -15,7 +14,7 @@ import org.fossify.commons.extensions.launchViewIntent
import org.fossify.commons.helpers.*
import org.fossify.commons.models.License

class LicenseActivity : ComponentActivity() {
class LicenseActivity : BaseComposeActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdgeSimple()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package org.fossify.commons.adapters

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.biometric.auth.AuthPromptHost
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import org.fossify.commons.R
import org.fossify.commons.helpers.PROTECTION_FINGERPRINT
import org.fossify.commons.helpers.PROTECTION_PATTERN
import org.fossify.commons.helpers.PROTECTION_PIN
import org.fossify.commons.helpers.isRPlus
import org.fossify.commons.interfaces.HashListener
import org.fossify.commons.interfaces.SecurityTab

class AppLockAdapter(
private val context: Context,
private val requiredHash: String,
private val hashListener: HashListener,
private val viewPager: ViewPager2,
private val biometricPromptHost: AuthPromptHost,
private val showBiometricIdTab: Boolean,
private val showBiometricAuthentication: Boolean
) : RecyclerView.Adapter<AppLockAdapter.ViewHolder>() {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(context).inflate(layoutSelection(viewType), parent, false)
view.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
return ViewHolder(view)
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind()

override fun getItemCount(): Int = if (showBiometricIdTab) 3 else 2

override fun getItemViewType(position: Int) = position

private fun layoutSelection(position: Int): Int = when (position) {
PROTECTION_PATTERN -> R.layout.tab_pattern
PROTECTION_PIN -> R.layout.tab_pin
PROTECTION_FINGERPRINT -> if (isRPlus()) R.layout.tab_biometric_id else R.layout.tab_fingerprint
else -> throw RuntimeException("Only 3 tabs allowed")
}

fun isTabVisible(position: Int, isVisible: Boolean) {
val viewHolder = (viewPager.getChildAt(0) as? RecyclerView)?.findViewHolderForAdapterPosition(position) as? ViewHolder
viewHolder?.itemView?.let {
(it as SecurityTab).visibilityChanged(isVisible)
}
}

inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind() {
(itemView as SecurityTab).initTab(
requiredHash = requiredHash,
listener = hashListener,
scrollView = null,
biometricPromptHost = biometricPromptHost,
showBiometricAuthentication = showBiometricAuthentication
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class SecurityDialog(
hashListener = this@SecurityDialog,
scrollView = dialogScrollview,
biometricPromptHost = AuthPromptHost(activity as FragmentActivity),
showBiometricIdTab = shouldShowBiometricIdTab(),
showBiometricIdTab = activity.isBiometricAuthSupported(),
showBiometricAuthentication = showTabIndex == PROTECTION_FINGERPRINT && isRPlus()
)
viewPager.adapter = tabsAdapter
Expand All @@ -49,7 +49,7 @@ class SecurityDialog(
if (showTabIndex == SHOW_ALL_TABS) {
val textColor = root.context.getProperTextColor()

if (shouldShowBiometricIdTab()) {
if (activity.isBiometricAuthSupported()) {
val tabTitle = if (isRPlus()) R.string.biometrics else R.string.fingerprint
dialogTabLayout.addTab(dialogTabLayout.newTab().setText(tabTitle), PROTECTION_FINGERPRINT)
}
Expand Down Expand Up @@ -107,12 +107,4 @@ class SecurityDialog(
tabsAdapter.isTabVisible(i, viewPager.currentItem == i)
}
}

private fun shouldShowBiometricIdTab(): Boolean {
return if (isRPlus()) {
activity.isBiometricIdAvailable()
} else {
activity.isFingerPrintSensorAvailable()
}
}
}
10 changes: 10 additions & 0 deletions commons/src/main/kotlin/org/fossify/commons/extensions/Activity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1643,3 +1643,13 @@ fun Activity.onApplyWindowInsets(callback: (WindowInsetsCompat) -> Unit) {
insets
}
}

fun Activity.overrideActivityTransition(enterAnim: Int, exitAnim: Int, exiting: Boolean = false) {
if (isUpsideDownCakePlus()) {
val overrideType = if (exiting) Activity.OVERRIDE_TRANSITION_CLOSE else Activity.OVERRIDE_TRANSITION_OPEN
overrideActivityTransition(overrideType, enterAnim, exitAnim)
} else {
@Suppress("DEPRECATION")
overridePendingTransition(enterAnim, exitAnim)
}
}
Loading

0 comments on commit 1e5b935

Please sign in to comment.