-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Make inactivity timeout configurable and add explicit lock button
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
- Loading branch information
1 parent
8b5c8eb
commit aa330ba
Showing
8 changed files
with
264 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
/* | ||
* SPDX-FileCopyrightText: 2023-2024 Andrew Gunnerson | ||
* SPDX-License-Identifier: GPL-3.0-only | ||
*/ | ||
|
||
package com.chiller3.rsaf | ||
|
||
import android.content.Context | ||
import android.util.Log | ||
|
||
object AppLock { | ||
private sealed interface State { | ||
val isLocked: Boolean | ||
|
||
data object Active : State { | ||
override val isLocked: Boolean = false | ||
} | ||
|
||
data class Inactive(val pauseTime: Long) : State { | ||
override val isLocked: Boolean | ||
get() = System.nanoTime() - pauseTime >= prefs.inactivityTimeout * 1_000_000_000L | ||
} | ||
|
||
data object Locked : State { | ||
override val isLocked: Boolean = true | ||
} | ||
} | ||
|
||
private val TAG = AppLock::class.java.simpleName | ||
|
||
private lateinit var appContext: Context | ||
private lateinit var prefs: Preferences | ||
private var state: State = State.Locked | ||
set(s) { | ||
Log.d(TAG, "State changed: $s") | ||
field = s | ||
} | ||
|
||
val isLocked: Boolean | ||
get() = state.isLocked | ||
|
||
fun init(context: Context) { | ||
appContext = context.applicationContext | ||
prefs = Preferences(appContext) | ||
|
||
if (!prefs.requireAuth) { | ||
state = State.Active | ||
} | ||
} | ||
|
||
fun onAppResume() { | ||
state.let { | ||
if (it is State.Inactive) { | ||
if (it.isLocked) { | ||
Log.d(TAG, "Timed out due to inactivity") | ||
state = State.Locked | ||
} else { | ||
Log.d(TAG, "App is active again") | ||
state = State.Active | ||
} | ||
} | ||
} | ||
} | ||
|
||
fun onAppPause() { | ||
if (prefs.requireAuth && state == State.Active) { | ||
Log.d(TAG, "App is inactive") | ||
state = State.Inactive(System.nanoTime()) | ||
} | ||
} | ||
|
||
fun onAuthSuccess() { | ||
Log.d(TAG, "Authentication succeeded") | ||
state = State.Active | ||
} | ||
|
||
fun onLock() { | ||
if (prefs.requireAuth) { | ||
Log.d(TAG, "User requested immediate locking") | ||
state = State.Locked | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
92 changes: 92 additions & 0 deletions
92
app/src/main/java/com/chiller3/rsaf/dialog/InactivityTimeoutDialogFragment.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
/* | ||
* SPDX-FileCopyrightText: 2024 Andrew Gunnerson | ||
* SPDX-License-Identifier: GPL-3.0-only | ||
*/ | ||
|
||
package com.chiller3.rsaf.dialog | ||
|
||
import android.annotation.SuppressLint | ||
import android.app.Dialog | ||
import android.content.DialogInterface | ||
import android.os.Bundle | ||
import android.text.InputType | ||
import android.view.Gravity | ||
import androidx.appcompat.app.AlertDialog | ||
import androidx.core.os.bundleOf | ||
import androidx.core.widget.addTextChangedListener | ||
import androidx.fragment.app.DialogFragment | ||
import androidx.fragment.app.setFragmentResult | ||
import com.chiller3.rsaf.Preferences | ||
import com.chiller3.rsaf.R | ||
import com.chiller3.rsaf.databinding.DialogTextInputBinding | ||
import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||
|
||
class InactivityTimeoutDialogFragment : DialogFragment() { | ||
companion object { | ||
val TAG = InactivityTimeoutDialogFragment::class.java.simpleName | ||
|
||
const val RESULT_SUCCESS = "success" | ||
} | ||
|
||
private lateinit var prefs: Preferences | ||
private lateinit var binding: DialogTextInputBinding | ||
private var duration: Int? = null | ||
private var success: Boolean = false | ||
|
||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { | ||
prefs = Preferences(requireContext()) | ||
|
||
binding = DialogTextInputBinding.inflate(layoutInflater) | ||
binding.message.text = getString(R.string.dialog_inactivity_timeout_message) | ||
binding.text.inputType = InputType.TYPE_CLASS_NUMBER | ||
binding.text.addTextChangedListener { | ||
duration = try { | ||
val seconds = it.toString().toInt() | ||
if (seconds >= Preferences.MIN_INACTIVITY_TIMEOUT) { | ||
seconds | ||
} else { | ||
null | ||
} | ||
} catch (_: Exception) { | ||
null | ||
} | ||
|
||
refreshOkButtonEnabledState() | ||
} | ||
if (savedInstanceState == null) { | ||
@SuppressLint("SetTextI18n") | ||
binding.text.setText(prefs.inactivityTimeout.toString()) | ||
} | ||
|
||
return MaterialAlertDialogBuilder(requireContext()) | ||
.setTitle(R.string.dialog_inactivity_timeout_title) | ||
.setView(binding.root) | ||
.setPositiveButton(R.string.dialog_action_ok) { _, _ -> | ||
prefs.inactivityTimeout = duration!! | ||
success = true | ||
} | ||
.setNegativeButton(R.string.dialog_action_cancel, null) | ||
.create() | ||
.apply { | ||
if (Preferences(requireContext()).dialogsAtBottom) { | ||
window!!.attributes.gravity = Gravity.BOTTOM | ||
} | ||
} | ||
} | ||
|
||
override fun onStart() { | ||
super.onStart() | ||
refreshOkButtonEnabledState() | ||
} | ||
|
||
override fun onDismiss(dialog: DialogInterface) { | ||
super.onDismiss(dialog) | ||
|
||
setFragmentResult(tag!!, bundleOf(RESULT_SUCCESS to success)) | ||
} | ||
|
||
private fun refreshOkButtonEnabledState() { | ||
(dialog as AlertDialog?)?.getButton(AlertDialog.BUTTON_POSITIVE)?.isEnabled = | ||
duration != null | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.