This commit is contained in:
Milos Kozak 2020-04-21 22:20:14 +02:00
commit 92ed678b6e
17 changed files with 164 additions and 36 deletions

View file

@ -322,7 +322,9 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
context?.let { context ->
if (preference != null) {
if (preference.key == resourceHelper.gs(R.string.key_master_password)) {
passwordCheck.setPassword(context, R.string.master_password, R.string.key_master_password)
passwordCheck.queryPassword(context, R.string.current_master_password, R.string.key_master_password, {
passwordCheck.setPassword(context, R.string.master_password, R.string.key_master_password)
})
return true
}
if (preference.key == resourceHelper.gs(R.string.key_settings_password)) {

View file

@ -165,6 +165,7 @@ interface AppComponent : AndroidInjector<MainApp> {
fun injectSWButton(swButton: SWButton)
fun injectSWEditNumberWithUnits(swEditNumberWithUnits: SWEditNumberWithUnits)
fun injectSWEditString(swEditString: SWEditString)
fun injectSWEditEncryptedPassword(swSWEditEncryptedPassword: SWEditEncryptedPassword)
fun injectSWEditUrl(swEditUrl: SWEditUrl)
fun injectSWFragment(swFragment: SWFragment)
fun injectSSWHtmlLink(swHtmlLink: SWHtmlLink)

View file

@ -246,6 +246,7 @@ open class AppModule {
@ContributesAndroidInjector fun swButtonInjector(): SWButton
@ContributesAndroidInjector fun swEditNumberWithUnitsInjector(): SWEditNumberWithUnits
@ContributesAndroidInjector fun swEditStringInjector(): SWEditString
@ContributesAndroidInjector fun swEditEncryptedPasswordInjector(): SWEditEncryptedPassword
@ContributesAndroidInjector fun swEditUrlInjector(): SWEditUrl
@ContributesAndroidInjector fun swFragmentInjector(): SWFragment
@ContributesAndroidInjector fun swHtmlLinkInjector(): SWHtmlLink

View file

@ -15,5 +15,6 @@ abstract class ReceiversModule {
@ContributesAndroidInjector abstract fun contributesKeepAliveReceiver(): KeepAliveReceiver
@ContributesAndroidInjector abstract fun contributesNetworkChangeReceiver(): NetworkChangeReceiver
@ContributesAndroidInjector abstract fun contributesRileyLinkBluetoothStateReceiver(): RileyLinkBluetoothStateReceiver
@ContributesAndroidInjector abstract fun contributesSmsReceiver(): SmsReceiver
@ContributesAndroidInjector abstract fun contributesTimeDateOrTZChangeReceiver(): TimeDateOrTZChangeReceiver
}

View file

@ -382,6 +382,7 @@ public class LoopPlugin extends PluginBase {
if (lastRun == null) lastRun = new LastRun();
lastRun.request = result;
lastRun.constraintsProcessed = resultAfterConstraints;
lastRun.lastAPSRun = DateUtil.now();
lastRun.source = ((PluginBase) usedAPS).getName();
lastRun.tbrSetByPump = null;
lastRun.smbSetByPump = null;

View file

@ -165,7 +165,7 @@ public class NSUpload {
Profile profile = profileFunction.getProfile();
String profileName = profileFunction.getProfileName();
if (profile == null || profileName == null) {
if (profile == null) {
log.error("Profile is null. Skipping upload");
return;
}

View file

@ -755,6 +755,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
// ****** GRAPH *******
GlobalScope.launch(Dispatchers.Main) {
overview_bggraph?: return@launch
val graphData = GraphData(injector, overview_bggraph, iobCobCalculatorPlugin)
val secondaryGraphsData: ArrayList<GraphData> = ArrayList()

View file

@ -29,7 +29,7 @@ class NetworkChangeReceiver : DaggerBroadcastReceiver() {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networks: Array<Network> = cm.allNetworks
networks.forEach {
val capabilities = cm.getNetworkCapabilities(it)
val capabilities = cm.getNetworkCapabilities(it) ?: return@forEach
event.wifiConnected = event.wifiConnected || (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
|| capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET))
event.mobileConnected = event.mobileConnected || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)

View file

@ -33,6 +33,7 @@ import info.nightscout.androidaps.plugins.profile.ns.NSProfilePlugin
import info.nightscout.androidaps.setupwizard.elements.*
import info.nightscout.androidaps.setupwizard.events.EventSWUpdate
import info.nightscout.androidaps.utils.AndroidPermission
import info.nightscout.androidaps.utils.CryptoUtil
import info.nightscout.androidaps.utils.LocaleHelper.update
import info.nightscout.androidaps.utils.extensions.isRunningTest
import info.nightscout.androidaps.utils.protection.ProtectionCheck
@ -60,7 +61,8 @@ class SWDefinition @Inject constructor(
private val nsProfilePlugin: NSProfilePlugin,
private val protectionCheck: ProtectionCheck,
private val importExportPrefs: ImportExportPrefs,
private val androidPermission: AndroidPermission
private val androidPermission: AndroidPermission,
private val cryptoUtil: CryptoUtil
) {
lateinit var activity: AppCompatActivity
@ -203,9 +205,18 @@ class SWDefinition @Inject constructor(
.add(SWInfotext(injector)
.label(R.string.patient_name_summary))
.add(SWEditString(injector)
.validator(SWTextValidator { text: String -> text.length > 0 })
.preferenceId(R.string.key_patient_name)
.updateDelay(5))
.validator(SWTextValidator(String::isNotEmpty))
.preferenceId(R.string.key_patient_name))
private val screenMasterPassword = SWScreen(injector, R.string.master_password)
.skippable(false)
.add(SWInfotext(injector)
.label(R.string.master_password))
.add(SWEditEncryptedPassword(injector, cryptoUtil)
.preferenceId(R.string.key_master_password))
.add(SWBreak(injector))
.add(SWInfotext(injector)
.label(R.string.master_password_summary))
.validator(SWValidator { !cryptoUtil.checkPassword("", sp.getString(R.string.key_master_password, "")) })
private val screenAge = SWScreen(injector, R.string.patientage)
.skippable(false)
.add(SWBreak(injector))
@ -393,6 +404,7 @@ class SWDefinition @Inject constructor(
.add(if (isRunningTest()) null else screenPermissionBattery) // cannot mock ask battery optimization
.add(screenPermissionBt)
.add(screenPermissionStore)
.add(screenMasterPassword)
.add(screenImport)
.add(screenUnits)
.add(displaySettings)
@ -420,6 +432,7 @@ class SWDefinition @Inject constructor(
.add(if (isRunningTest()) null else screenPermissionBattery) // cannot mock ask battery optimization
.add(screenPermissionBt)
.add(screenPermissionStore)
.add(screenMasterPassword)
.add(screenImport)
.add(screenUnits)
.add(displaySettings)
@ -442,6 +455,7 @@ class SWDefinition @Inject constructor(
.add(screenEula)
.add(if (isRunningTest()) null else screenPermissionBattery) // cannot mock ask battery optimization
.add(screenPermissionStore)
.add(screenMasterPassword)
.add(screenImport)
.add(screenUnits)
.add(displaySettings)

View file

@ -0,0 +1,82 @@
package info.nightscout.androidaps.setupwizard.elements
import android.graphics.Typeface
import android.text.Editable
import android.text.InputType
import android.text.TextWatcher
import android.view.View
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.TextView
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.setupwizard.SWTextValidator
import info.nightscout.androidaps.utils.CryptoUtil
class SWEditEncryptedPassword(injector: HasAndroidInjector, private val cryptoUtil: CryptoUtil) : SWItem(injector, Type.STRING) {
private var validator: SWTextValidator = SWTextValidator(String::isNotEmpty)
private var updateDelay = 0L
override fun generateDialog(layout: LinearLayout) {
val context = layout.context
val l = TextView(context)
l.id = View.generateViewId()
label?.let { l.setText(it) }
l.setTypeface(l.typeface, Typeface.BOLD)
layout.addView(l)
val c = TextView(context)
c.id = View.generateViewId()
comment?.let { c.setText(it) }
c.setTypeface(c.typeface, Typeface.ITALIC)
layout.addView(c)
val editText = EditText(context)
editText.id = View.generateViewId()
editText.inputType = InputType.TYPE_CLASS_TEXT
editText.maxLines = 1
editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
layout.addView(editText)
val c2 = TextView(context)
c2.id = View.generateViewId()
c2.setText(R.string.confirm)
layout.addView(c2)
val editText2 = EditText(context)
editText2.id = View.generateViewId()
editText2.inputType = InputType.TYPE_CLASS_TEXT
editText2.maxLines = 1
editText2.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
layout.addView(editText2)
super.generateDialog(layout)
val watcher = object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
sp.remove(preferenceId)
scheduleChange(updateDelay)
if (validator.isValid(editText.text.toString()) && validator.isValid(editText2.text.toString()) && editText.text.toString() == editText2.text.toString())
save(s.toString(), updateDelay)
}
override fun afterTextChanged(s: Editable) {}
}
editText.addTextChangedListener(watcher)
editText2.addTextChangedListener(watcher)
}
fun preferenceId(preferenceId: Int): SWEditEncryptedPassword {
this.preferenceId = preferenceId
return this
}
fun validator(validator: SWTextValidator): SWEditEncryptedPassword {
this.validator = validator
return this
}
override fun save(value: String, updateDelay: Long) {
sp.putString(preferenceId, cryptoUtil.hashPassword(value))
scheduleChange(updateDelay)
}
}

View file

@ -10,6 +10,7 @@ import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.setupwizard.events.EventSWUpdate
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.sharedPreferences.SP
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
@ -20,7 +21,7 @@ open class SWItem(val injector: HasAndroidInjector, var type: Type) {
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var rxBus: RxBusWrapper
@Inject lateinit var resourceHelper: ResourceHelper
@Inject lateinit var sp: info.nightscout.androidaps.utils.sharedPreferences.SP
@Inject lateinit var sp: SP
private val eventWorker = Executors.newSingleThreadScheduledExecutor()
private var scheduledEventPost: ScheduledFuture<*>? = null
@ -55,7 +56,7 @@ open class SWItem(val injector: HasAndroidInjector, var type: Type) {
return this
}
fun save(value: String, updateDelay: Long) {
open fun save(value: String, updateDelay: Long) {
sp.putString(preferenceId, value)
scheduleChange(updateDelay)
}
@ -69,7 +70,7 @@ open class SWItem(val injector: HasAndroidInjector, var type: Type) {
open fun generateDialog(layout: LinearLayout) {}
open fun processVisibility() {}
private fun scheduleChange(updateDelay: Long) {
fun scheduleChange(updateDelay: Long) {
class PostRunnable : Runnable {
override fun run() {
aapsLogger.debug(LTag.CORE, "Firing EventPreferenceChange")

View file

@ -5,10 +5,11 @@ import androidx.biometric.BiometricPrompt
import androidx.fragment.app.FragmentActivity
import info.nightscout.androidaps.R
import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.extensions.runOnUiThread
import java.util.concurrent.Executors
object BiometricCheck {
fun biometricPrompt(activity: FragmentActivity, title: Int, ok: Runnable?, cancel: Runnable? = null, fail: Runnable? = null) {
fun biometricPrompt(activity: FragmentActivity, title: Int, ok: Runnable?, cancel: Runnable? = null, fail: Runnable? = null, passwordCheck: PasswordCheck) {
val executor = Executors.newSingleThreadExecutor()
val biometricPrompt = BiometricPrompt(activity, executor, object : BiometricPrompt.AuthenticationCallback() {
@ -23,24 +24,31 @@ object BiometricCheck {
BiometricConstants.ERROR_LOCKOUT_PERMANENT,
BiometricConstants.ERROR_USER_CANCELED -> {
ToastUtils.showToastInUiThread(activity.baseContext, errString.toString())
fail?.run()
// fallback to master password
runOnUiThread(Runnable {
passwordCheck.queryPassword(activity, R.string.master_password, R.string.key_master_password, { ok?.run() }, { cancel?.run() }, { fail?.run() })
})
}
BiometricConstants.ERROR_NEGATIVE_BUTTON ->
cancel?.run()
BiometricConstants.ERROR_NO_DEVICE_CREDENTIAL ->
// call ok, because it's not possible to bypass it when biometrics is setup, hw not present and no pin set
ok?.run()
BiometricConstants.ERROR_NO_DEVICE_CREDENTIAL -> {
ToastUtils.showToastInUiThread(activity.baseContext, errString.toString())
// no pin set
// fallback to master password
runOnUiThread(Runnable {
passwordCheck.queryPassword(activity, R.string.master_password, R.string.key_master_password, { ok?.run() }, { cancel?.run() }, { fail?.run() })
})
}
BiometricConstants.ERROR_NO_SPACE,
BiometricConstants.ERROR_HW_UNAVAILABLE,
BiometricConstants.ERROR_HW_NOT_PRESENT,
BiometricConstants.ERROR_NO_BIOMETRICS ->
// call ok, because it's not possible to bypass it when biometrics fail
// ok?.run()
// changed to fail as you can use PIN instead with setDeviceCredentialAllowed enabled
fail?.run()
runOnUiThread(Runnable {
passwordCheck.queryPassword(activity, R.string.master_password, R.string.key_master_password, { ok?.run() }, { cancel?.run() }, { fail?.run() })
})
}
}
@ -60,8 +68,8 @@ object BiometricCheck {
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle(activity.getString(title))
.setDescription(activity.getString(R.string.biometric_title))
// .setNegativeButtonText(activity.getString(R.string.cancel)) // not possible with setDeviceCredentialAllowed
.setDeviceCredentialAllowed(true)
.setNegativeButtonText(activity.getString(R.string.cancel)) // not possible with setDeviceCredentialAllowed
// .setDeviceCredentialAllowed(true) // setDeviceCredentialAllowed creates new activity when PIN is requested, activity.fragmentManager crash afterwards
.build()
biometricPrompt.authenticate(promptInfo)

View file

@ -16,19 +16,19 @@ import javax.inject.Inject
import javax.inject.Singleton
// since androidx.autofill.HintConstants are not available
val AUTOFILL_HINT_NEW_PASSWORD = "newPassword"
const val AUTOFILL_HINT_NEW_PASSWORD = "newPassword"
@Singleton
class PasswordCheck @Inject constructor(
val sp: SP,
val cryptoUtil: CryptoUtil
private val cryptoUtil: CryptoUtil
) {
@SuppressLint("InflateParams")
fun queryPassword(context: Context, @StringRes labelId: Int, @StringRes preference: Int, ok: ( (String) -> Unit)?, cancel: (()->Unit)? = null, fail: (()->Unit)? = null) {
fun queryPassword(context: Context, @StringRes labelId: Int, @StringRes preference: Int, ok: ((String) -> Unit)?, cancel: (() -> Unit)? = null, fail: (() -> Unit)? = null) {
val password = sp.getString(preference, "")
if (password == "") {
ok?.invoke("")
ok?.invoke("")
return
}
@ -36,7 +36,11 @@ class PasswordCheck @Inject constructor(
val alertDialogBuilder = AlertDialogHelper.Builder(context)
alertDialogBuilder.setView(promptsView)
val userInput = promptsView.findViewById<View>(R.id.passwordprompt_pass) as EditText
val userInput = promptsView.findViewById<View>(R.id.password_prompt_pass) as EditText
val userInput2 = promptsView.findViewById<View>(R.id.password_prompt_pass_confirm) as EditText
userInput2.visibility = View.GONE
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val autoFillHintPasswordKind = context.getString(preference)
userInput.setAutofillHints(View.AUTOFILL_HINT_PASSWORD, "aaps_${autoFillHintPasswordKind}")
@ -64,12 +68,13 @@ class PasswordCheck @Inject constructor(
}
@SuppressLint("InflateParams")
fun setPassword(context: Context, @StringRes labelId: Int, @StringRes preference: Int, ok: ( (String) -> Unit)? = null, cancel: (()->Unit)? = null, clear: (()->Unit)? = null) {
fun setPassword(context: Context, @StringRes labelId: Int, @StringRes preference: Int, ok: ((String) -> Unit)? = null, cancel: (() -> Unit)? = null, clear: (() -> Unit)? = null) {
val promptsView = LayoutInflater.from(context).inflate(R.layout.passwordprompt, null)
val alertDialogBuilder = AlertDialogHelper.Builder(context)
alertDialogBuilder.setView(promptsView)
val userInput = promptsView.findViewById<View>(R.id.passwordprompt_pass) as EditText
val userInput = promptsView.findViewById<View>(R.id.password_prompt_pass) as EditText
val userInput2 = promptsView.findViewById<View>(R.id.password_prompt_pass_confirm) as EditText
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val autoFillHintPasswordKind = context.getString(preference)
@ -82,7 +87,10 @@ class PasswordCheck @Inject constructor(
.setCustomTitle(AlertDialogHelper.buildCustomTitle(context, context.getString(labelId), R.drawable.ic_header_key))
.setPositiveButton(context.getString(R.string.ok)) { _, _ ->
val enteredPassword = userInput.text.toString()
if (enteredPassword.isNotEmpty()) {
val enteredPassword2 = userInput2.text.toString()
if (enteredPassword != enteredPassword2) {
ToastUtils.errorToast(context, context.getString(R.string.passwords_dont_match))
} else if (enteredPassword.isNotEmpty()) {
sp.putString(preference, cryptoUtil.hashPassword(enteredPassword))
ToastUtils.okToast(context, context.getString(R.string.password_set))
ok?.invoke(enteredPassword)

View file

@ -56,7 +56,7 @@ class ProtectionCheck @Inject constructor(
ProtectionType.NONE ->
ok?.run()
ProtectionType.BIOMETRIC ->
BiometricCheck.biometricPrompt(activity, titleResourceIDs[protection.ordinal], ok, cancel, fail)
BiometricCheck.biometricPrompt(activity, titleResourceIDs[protection.ordinal], ok, cancel, fail, passwordCheck)
ProtectionType.MASTER_PASSWORD ->
passwordCheck.queryPassword(activity, R.string.master_password, R.string.key_master_password, { ok?.run() }, { cancel?.run() }, { fail?.run() })
ProtectionType.CUSTOM_PASSWORD ->

View file

@ -3,18 +3,23 @@
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:padding="10dp" >
android:padding="10dp">
<EditText
android:id="@+id/passwordprompt_pass"
android:id="@+id/password_prompt_pass"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password_hint"
android:inputType="textPassword"
>
android:inputType="textPassword">
<requestFocus />
</EditText>
<EditText
android:id="@+id/password_prompt_pass_confirm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password_hint"
android:inputType="textPassword"/>
</LinearLayout>

View file

@ -34,7 +34,7 @@
<string name="objectives_smb_objective">Enabling additional features for daytime use, such as SMB</string>
<string name="objectives_auto_objective">Enabling automation</string>
<string name="objectives_smb_gate">You must read the wiki and rise maxIOB to get SMBs working fine! A good start is maxIOB=average mealbolus + 3 x max daily basal</string>
<string name="objectives_auto_gate">Read the wiki how automation works. Setup your first simple rules. Instead of action let AAPS display only notification. When you are sure automation is triggered at the right time replace notification by real action.</string>
<string name="objectives_auto_gate">Read the docs how automation works. Setup your first simple rules. Instead of action let AAPS display only notification. When you are sure automation is triggered at the right time replace notification by real action. (https://androidaps.readthedocs.io/en/latest/EN/Usage/Automation.html)</string>
<string name="objectives_bgavailableinns">BG available in NS</string>
<string name="objectives_pumpstatusavailableinns">Pump status available in NS</string>
<string name="objectives_manualenacts">Manual enacts</string>

View file

@ -1805,4 +1805,7 @@
<string name="key_graphconfig" translatable="false">graphconfig</string>
<string name="authorizationfailed">Authorization failed</string>
<string name="overview_show_absinsulin">Absolute insulin</string>
<string name="master_password_summary">Master password is used for backup encryption and to override security in application. Remember it or store on a safe place.</string>
<string name="passwords_dont_match">Passwords don\'t match</string>
<string name="current_master_password">Current master password</string>
</resources>