Merge pull request #672 from esensar/feature/446-password-protection

Add password protection feature to the app
This commit is contained in:
Tibor Kaputa 2023-06-30 14:43:20 +02:00 committed by GitHub
commit b0da7bad31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 177 additions and 47 deletions

View File

@ -21,7 +21,7 @@
<uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission <uses-permission
android:name="android.permission.USE_FINGERPRINT" android:name="android.permission.USE_BIOMETRIC"
tools:node="remove" /> tools:node="remove" />
<queries> <queries>
@ -55,9 +55,9 @@
android:name=".activities.ThreadActivity" android:name=".activities.ThreadActivity"
android:configChanges="orientation" android:configChanges="orientation"
android:exported="false" android:exported="false"
android:launchMode="singleTop"
android:parentActivityName=".activities.MainActivity" android:parentActivityName=".activities.MainActivity"
android:windowSoftInputMode="adjustResize" android:windowSoftInputMode="adjustResize" />
android:launchMode="singleTop" />
<activity <activity
android:name=".activities.NewConversationActivity" android:name=".activities.NewConversationActivity"

View File

@ -50,6 +50,7 @@ class MainActivity : SimpleActivity() {
private var lastSearchedText = "" private var lastSearchedText = ""
private var bus: EventBus? = null private var bus: EventBus? = null
private val smsExporter by lazy { MessagesExporter(this) } private val smsExporter by lazy { MessagesExporter(this) }
private var wasProtectionHandled = false
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -62,31 +63,19 @@ class MainActivity : SimpleActivity() {
updateMaterialActivityViews(main_coordinator, conversations_list, useTransparentNavigation = true, useTopSearchMenu = true) updateMaterialActivityViews(main_coordinator, conversations_list, useTransparentNavigation = true, useTopSearchMenu = true)
if (checkAppSideloading()) { if (savedInstanceState == null) {
return handleAppPasswordProtection {
wasProtectionHandled = it
if (it) {
loadMessages()
} else {
finish()
}
}
} }
if (isQPlus()) { if (checkAppSideloading()) {
val roleManager = getSystemService(RoleManager::class.java) return
if (roleManager!!.isRoleAvailable(RoleManager.ROLE_SMS)) {
if (roleManager.isRoleHeld(RoleManager.ROLE_SMS)) {
askPermissions()
} else {
val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_SMS)
startActivityForResult(intent, MAKE_DEFAULT_APP_REQUEST)
}
} else {
toast(R.string.unknown_error_occurred)
finish()
}
} else {
if (Telephony.Sms.getDefaultSmsPackage(this) == packageName) {
askPermissions()
} else {
val intent = Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT)
intent.putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, packageName)
startActivityForResult(intent, MAKE_DEFAULT_APP_REQUEST)
}
} }
clearAllMessagesIfNeeded() clearAllMessagesIfNeeded()
@ -140,6 +129,29 @@ class MainActivity : SimpleActivity() {
} }
} }
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putBoolean(WAS_PROTECTION_HANDLED, wasProtectionHandled)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
wasProtectionHandled = savedInstanceState.getBoolean(WAS_PROTECTION_HANDLED, false)
if (!wasProtectionHandled) {
handleAppPasswordProtection {
wasProtectionHandled = it
if (it) {
loadMessages()
} else {
finish()
}
}
} else {
loadMessages()
}
}
private fun setupOptionsMenu() { private fun setupOptionsMenu() {
main_menu.getToolbar().inflateMenu(R.menu.menu_main) main_menu.getToolbar().inflateMenu(R.menu.menu_main)
main_menu.toggleHideOnScroll(true) main_menu.toggleHideOnScroll(true)
@ -205,6 +217,31 @@ class MainActivity : SimpleActivity() {
main_menu.updateColors() main_menu.updateColors()
} }
private fun loadMessages() {
if (isQPlus()) {
val roleManager = getSystemService(RoleManager::class.java)
if (roleManager!!.isRoleAvailable(RoleManager.ROLE_SMS)) {
if (roleManager.isRoleHeld(RoleManager.ROLE_SMS)) {
askPermissions()
} else {
val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_SMS)
startActivityForResult(intent, MAKE_DEFAULT_APP_REQUEST)
}
} else {
toast(R.string.unknown_error_occurred)
finish()
}
} else {
if (Telephony.Sms.getDefaultSmsPackage(this) == packageName) {
askPermissions()
} else {
val intent = Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT)
intent.putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, packageName)
startActivityForResult(intent, MAKE_DEFAULT_APP_REQUEST)
}
}
}
// while SEND_SMS and READ_SMS permissions are mandatory, READ_CONTACTS is optional. If we don't have it, we just won't be able to show the contact name in some cases // while SEND_SMS and READ_SMS permissions are mandatory, READ_CONTACTS is optional. If we don't have it, we just won't be able to show the contact name in some cases
private fun askPermissions() { private fun askPermissions() {
handlePermission(PERMISSION_READ_SMS) { handlePermission(PERMISSION_READ_SMS) {
@ -410,6 +447,7 @@ class MainActivity : SimpleActivity() {
val conversation = any as Conversation val conversation = any as Conversation
putExtra(THREAD_ID, conversation.threadId) putExtra(THREAD_ID, conversation.threadId)
putExtra(THREAD_TITLE, conversation.title) putExtra(THREAD_TITLE, conversation.title)
putExtra(WAS_PROTECTION_HANDLED, wasProtectionHandled)
startActivity(this) startActivity(this)
} }
} }
@ -636,6 +674,7 @@ class MainActivity : SimpleActivity() {
showErrorToast(e) showErrorToast(e)
} }
} }
else -> toast(R.string.invalid_file_format) else -> toast(R.string.invalid_file_format)
} }
} }

View File

@ -5,9 +5,7 @@ import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import com.simplemobiletools.commons.activities.ManageBlockedNumbersActivity import com.simplemobiletools.commons.activities.ManageBlockedNumbersActivity
import com.simplemobiletools.commons.dialogs.ChangeDateTimeFormatDialog import com.simplemobiletools.commons.dialogs.*
import com.simplemobiletools.commons.dialogs.FeatureLockedDialog
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.* import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.commons.models.RadioItem import com.simplemobiletools.commons.models.RadioItem
@ -49,6 +47,7 @@ class SettingsActivity : SimpleActivity() {
setupGroupMessageAsMMS() setupGroupMessageAsMMS()
setupLockScreenVisibility() setupLockScreenVisibility()
setupMMSFileSizeLimit() setupMMSFileSizeLimit()
setupAppPasswordProtection()
updateTextColors(settings_nested_scrollview) updateTextColors(settings_nested_scrollview)
if (blockedNumbersAtPause != -1 && blockedNumbersAtPause != getBlockedNumbers().hashCode()) { if (blockedNumbersAtPause != -1 && blockedNumbersAtPause != getBlockedNumbers().hashCode()) {
@ -59,7 +58,8 @@ class SettingsActivity : SimpleActivity() {
settings_color_customization_section_label, settings_color_customization_section_label,
settings_general_settings_label, settings_general_settings_label,
settings_outgoing_messages_label, settings_outgoing_messages_label,
settings_notifications_label settings_notifications_label,
settings_security_label
).forEach { ).forEach {
it.setTextColor(getProperPrimaryColor()) it.setTextColor(getProperPrimaryColor())
} }
@ -244,6 +244,28 @@ class SettingsActivity : SimpleActivity() {
} }
} }
private fun setupAppPasswordProtection() {
settings_app_password_protection.isChecked = config.isAppPasswordProtectionOn
settings_app_password_protection_holder.setOnClickListener {
val tabToShow = if (config.isAppPasswordProtectionOn) config.appProtectionType else SHOW_ALL_TABS
SecurityDialog(this, config.appPasswordHash, tabToShow) { hash, type, success ->
if (success) {
val hasPasswordProtection = config.isAppPasswordProtectionOn
settings_app_password_protection.isChecked = !hasPasswordProtection
config.isAppPasswordProtectionOn = !hasPasswordProtection
config.appPasswordHash = if (hasPasswordProtection) "" else hash
config.appProtectionType = type
if (config.isAppPasswordProtectionOn) {
val confirmationTextId = if (config.appProtectionType == PROTECTION_FINGERPRINT)
R.string.fingerprint_setup_successfully else R.string.protection_setup_successfully
ConfirmationDialog(this, "", confirmationTextId, R.string.ok, 0) { }
}
}
}
}
}
private fun getMMSFileLimitText() = getString( private fun getMMSFileLimitText() = getString(
when (config.mmsFileSizeLimit) { when (config.mmsFileSizeLimit) {
FILE_SIZE_100_KB -> R.string.mms_file_size_limit_100kb FILE_SIZE_100_KB -> R.string.mms_file_size_limit_100kb

View File

@ -101,6 +101,7 @@ class ThreadActivity : SimpleActivity() {
private var loadingOlderMessages = false private var loadingOlderMessages = false
private var allMessagesFetched = false private var allMessagesFetched = false
private var oldestMessageDate = -1 private var oldestMessageDate = -1
private var wasProtectionHandled = false
private var isScheduledMessage: Boolean = false private var isScheduledMessage: Boolean = false
private var scheduledMessage: Message? = null private var scheduledMessage: Message? = null
@ -136,28 +137,23 @@ class ThreadActivity : SimpleActivity() {
intent.getStringExtra(THREAD_TITLE)?.let { intent.getStringExtra(THREAD_TITLE)?.let {
thread_toolbar.title = it thread_toolbar.title = it
} }
wasProtectionHandled = intent.getBooleanExtra(WAS_PROTECTION_HANDLED, false)
bus = EventBus.getDefault() bus = EventBus.getDefault()
bus!!.register(this) bus!!.register(this)
handlePermission(PERMISSION_READ_PHONE_STATE) { granted ->
if (granted) {
setupButtons()
setupConversation()
setupCachedMessages {
val searchedMessageId = intent.getLongExtra(SEARCHED_MESSAGE_ID, -1L)
intent.removeExtra(SEARCHED_MESSAGE_ID)
if (searchedMessageId != -1L) {
val index = threadItems.indexOfFirst { (it as? Message)?.id == searchedMessageId }
if (index != -1) {
thread_messages_list.smoothScrollToPosition(index)
}
}
setupThread() if (savedInstanceState == null) {
setupScrollFab() if (!wasProtectionHandled) {
handleAppPasswordProtection {
wasProtectionHandled = it
if (it) {
loadConversation()
} else {
finish()
}
} }
} else { } else {
finish() loadConversation()
} }
} }
@ -221,6 +217,29 @@ class ThreadActivity : SimpleActivity() {
bus?.unregister(this) bus?.unregister(this)
} }
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putBoolean(WAS_PROTECTION_HANDLED, wasProtectionHandled)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
wasProtectionHandled = savedInstanceState.getBoolean(WAS_PROTECTION_HANDLED, false)
if (!wasProtectionHandled) {
handleAppPasswordProtection {
wasProtectionHandled = it
if (it) {
loadConversation()
} else {
finish()
}
}
} else {
loadConversation()
}
}
private fun refreshMenuItems() { private fun refreshMenuItems() {
val firstPhoneNumber = participants.firstOrNull()?.phoneNumbers?.firstOrNull()?.value val firstPhoneNumber = participants.firstOrNull()?.phoneNumbers?.firstOrNull()?.value
thread_toolbar.menu.apply { thread_toolbar.menu.apply {
@ -546,6 +565,30 @@ class ThreadActivity : SimpleActivity() {
} }
} }
private fun loadConversation() {
handlePermission(PERMISSION_READ_PHONE_STATE) { granted ->
if (granted) {
setupButtons()
setupConversation()
setupCachedMessages {
val searchedMessageId = intent.getLongExtra(SEARCHED_MESSAGE_ID, -1L)
intent.removeExtra(SEARCHED_MESSAGE_ID)
if (searchedMessageId != -1L) {
val index = threadItems.indexOfFirst { (it as? Message)?.id == searchedMessageId }
if (index != -1) {
thread_messages_list.smoothScrollToPosition(index)
}
}
setupThread()
setupScrollFab()
}
} else {
finish()
}
}
}
private fun setupConversation() { private fun setupConversation() {
ensureBackgroundThread { ensureBackgroundThread {
conversation = conversationsDB.getConversationWithThreadId(threadId) conversation = conversationsDB.getConversationWithThreadId(threadId)

View File

@ -341,6 +341,32 @@
tools:text="@string/mms_file_size_limit_none" /> tools:text="@string/mms_file_size_limit_none" />
</RelativeLayout> </RelativeLayout>
<include
android:id="@+id/settings_outgoing_messages_divider"
layout="@layout/divider" />
<TextView
android:id="@+id/settings_security_label"
style="@style/SettingsSectionLabelStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/security" />
<RelativeLayout
android:id="@+id/settings_app_password_protection_holder"
style="@style/SettingsHolderCheckboxStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.simplemobiletools.commons.views.MyAppCompatCheckbox
android:id="@+id/settings_app_password_protection"
style="@style/SettingsCheckboxStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/password_protect_whole_app" />
</RelativeLayout>
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>