Skip to content
This repository has been archived by the owner on Jul 18, 2024. It is now read-only.

Commit

Permalink
app: LoginUtils: Add Save password and Auto login
Browse files Browse the repository at this point in the history
* 添加保存密码和自动登录
* 自动登录指登录过期后自动尝试执行一次登录操作
  • Loading branch information
YuKongA committed Apr 21, 2024
1 parent 2c25b45 commit 87fbb09
Show file tree
Hide file tree
Showing 35 changed files with 198 additions and 47 deletions.
62 changes: 55 additions & 7 deletions app/src/main/kotlin/top/yukonga/update/activity/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,22 @@ class MainActivity : AppCompatActivity() {
cookies.clear()
cookies["authResult"] = "-1"
FileUtils.saveCookiesFile(this@MainActivity, Json.encodeToString(cookies))
showStringToast(this@MainActivity, getString(R.string.login_expired_dialog), 0)
activityMainBinding.apply {
topAppBar.menu.findItem(R.id.login).isVisible = true
topAppBar.menu.findItem(R.id.logout).isVisible = false
if (prefs.getString("auto_login", "") == "1") {
showStringToast(this@MainActivity, getString(R.string.login_expired_auto), 1)
LoginUtils().login(
this@MainActivity,
LoginUtils().getAccountAndPassword(this@MainActivity).first,
LoginUtils().getAccountAndPassword(this@MainActivity).second,
prefs.getString("global", "") ?: "0",
prefs.getString("save_password", "") ?: "0",
true
)
} else {
showStringToast(this@MainActivity, getString(R.string.login_expired_dialog), 0)
activityMainBinding.apply {
topAppBar.menu.findItem(R.id.login).isVisible = true
topAppBar.menu.findItem(R.id.logout).isVisible = false
}
}
}
}
Expand Down Expand Up @@ -253,15 +265,32 @@ class MainActivity : AppCompatActivity() {
}
}
val inputAccountLayout = createTextInputLayout(getString(R.string.account))
val inputAccount = createTextInputEditText()
val inputAccount = createTextInputEditText().apply {
setText(LoginUtils().getAccountAndPassword(this@MainActivity).first)
}
inputAccountLayout.addView(inputAccount)
val inputPasswordLayout = createTextInputLayout(getString(R.string.password), TextInputLayout.END_ICON_PASSWORD_TOGGLE)
val inputPassword = createTextInputEditText(InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD)
val inputPassword = createTextInputEditText(InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD).apply {
setText(LoginUtils().getAccountAndPassword(this@MainActivity).second)
}
inputPasswordLayout.addView(inputPassword)
val savePasswordCheckBox = createCheckBox(
R.string.save_password, "save_password", createLayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f, 0)
)
val autoLoginCheckBox = createCheckBox(
R.string.auto_login, "auto_login", createLayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT, 0f, 27)
)
val linearLayout = LinearLayout(this).apply {
orientation = LinearLayout.HORIZONTAL
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
addView(savePasswordCheckBox)
addView(autoLoginCheckBox)
}
view.apply {
addView(title)
addView(inputAccountLayout)
addView(inputPasswordLayout)
addView(linearLayout)
}
MaterialAlertDialogBuilder(this@MainActivity).apply {
setView(view)
Expand All @@ -272,10 +301,11 @@ class MainActivity : AppCompatActivity() {
setPositiveButton(getString(R.string.login)) { _, _ ->
hapticConfirm(activityMainBinding.topAppBar)
val global = prefs.getString("global", "") ?: "0"
val savePassword = prefs.getString("save_password", "") ?: "0"
val mInputAccount = inputAccount.text.toString()
val mInputPassword = inputPassword.text.toString()
CoroutineScope(Dispatchers.Default).launch {
val isValid = LoginUtils().login(this@MainActivity, mInputAccount, mInputPassword, global)
val isValid = LoginUtils().login(this@MainActivity, mInputAccount, mInputPassword, global, savePassword)
if (isValid) {
withContext(Dispatchers.Main) {
mainContentBinding.apply {
Expand Down Expand Up @@ -550,6 +580,24 @@ class MainActivity : AppCompatActivity() {
}
}

fun createCheckBox(textId: Int, prefKey: String, layoutParams: LinearLayout.LayoutParams): MaterialCheckBox {
return MaterialCheckBox(this).apply {
isChecked = prefs.getString(prefKey, "") == "1"
setOnCheckedChangeListener { _, isChecked ->
prefs.edit().putString(prefKey, if (isChecked) "1" else "0").apply()
}
minimumHeight = 0
text = getString(textId)
this.layoutParams = layoutParams
}
}

fun createLayoutParams(width: Int, height: Int, weight: Float, marginEnd: Int): LinearLayout.LayoutParams {
return LinearLayout.LayoutParams(width, height, weight).apply {
setMargins(23.dp, 0.dp, marginEnd.dp, 0.dp)
}
}

private fun MaterialButton.setDownloadClickListener(fileName: String?, fileLink: String) {
setOnClickListener {
fileName?.let {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package top.yukonga.update.logic.utils

import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import java.security.KeyStore
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.GCMParameterSpec

object KeyStoreUtils {

private const val ANDROID_KEY_STORE = "AndroidKeyStore"
private const val UPDATER_KEY_ALIAS = "updater_key_alias"
private const val AES_MODE = "AES/GCM/NoPadding"

fun generateKey() {
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE)
keyGenerator.init(
KeyGenParameterSpec.Builder(UPDATER_KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM).setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE).setRandomizedEncryptionRequired(false)
.build()
)
keyGenerator.generateKey()
}

private fun getSecretKey(): SecretKey {
val keyStore = KeyStore.getInstance(ANDROID_KEY_STORE)
keyStore.load(null)
return keyStore.getKey(UPDATER_KEY_ALIAS, null) as SecretKey
}

fun getEncryptionCipher(): Cipher {
val cipher = Cipher.getInstance(AES_MODE)
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey())
return cipher
}

fun getDecryptionCipher(iv: ByteArray): Cipher {
val cipher = Cipher.getInstance(AES_MODE)
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), GCMParameterSpec(128, iv))
return cipher
}
}
63 changes: 57 additions & 6 deletions app/src/main/kotlin/top/yukonga/update/logic/utils/LoginUtils.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package top.yukonga.update.logic.utils

import android.content.Context
import androidx.preference.PreferenceManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.encodeToString
Expand All @@ -25,34 +26,37 @@ class LoginUtils {
private val loginAuth2Url = "https://account.xiaomi.com/pass/serviceLoginAuth2"
private val mediaType = "application/x-www-form-urlencoded".toMediaType()

suspend fun login(context: Context, account: String, password: String, global: String): Boolean {
suspend fun login(context: Context, account: String, password: String, global: String, savePassword: String, autoLogin: Boolean = false): Boolean {
if (account.isEmpty() || password.isEmpty()) {
withContext(Dispatchers.Main) {
showStringToast(context, context.getString(R.string.account_or_password_empty), 0)
}
return false
} else {
withContext(Dispatchers.Main) {
showStringToast(context, context.getString(R.string.logging_in), 1)
if (!autoLogin) showStringToast(context, context.getString(R.string.logging_in), 1)
}
}

if (savePassword == "1") saveAccountAndPassword(context, account, password) else deleteAccountAndPassword(context)


val md = MessageDigest.getInstance("MD5")
md.update(password.toByteArray())
val passwordHash = md.digest().joinToString("") { "%02x".format(it) }.uppercase()

val response1 = getRequest(loginUrl)
val _sign = response1.request.url.queryParameter("_sign")?.replace("2&V1_passport&", "")
if (_sign == null) {
val sign = response1.request.url.queryParameter("_sign")?.replace("2&V1_passport&", "")
if (sign == null) {
withContext(Dispatchers.Main) {
showStringToast(context, context.getString(R.string.request_sign_failed), 0)
}
return false
}

val sid = if (global == "1") "miuiota_intl" else "miuiromota"
val _locale = if (global == "1") "en_US" else "zh_CN"
val data = "_json=true&bizDeviceType=&user=$account&hash=$passwordHash&sid=$sid&_sign=$_sign&_locale=$_locale"
val locale = if (global == "1") "en_US" else "zh_CN"
val data = "_json=true&bizDeviceType=&user=$account&hash=$passwordHash&sid=$sid&_sign=$sign&_locale=$locale"
val requestBody = data.toRequestBody(mediaType)
val response2 = postRequest(loginAuth2Url, requestBody)

Expand Down Expand Up @@ -106,4 +110,51 @@ class LoginUtils {
}
}

private fun saveAccountAndPassword(context: Context, account: String, password: String) {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
val editor = sharedPreferences.edit()

KeyStoreUtils.generateKey()
val cipherAccount = KeyStoreUtils.getEncryptionCipher()
val encryptedAccount = cipherAccount.doFinal(account.toByteArray())
val cipherPassword = KeyStoreUtils.getEncryptionCipher()
val encryptedPassword = cipherPassword.doFinal(password.toByteArray())

editor.putString("account", Base64.getEncoder().encodeToString(encryptedAccount))
.putString("password", Base64.getEncoder().encodeToString(encryptedPassword))
.putString("account_iv", Base64.getEncoder().encodeToString(cipherAccount.iv))
.putString("password_iv", Base64.getEncoder().encodeToString(cipherPassword.iv)).apply()
}

fun getAccountAndPassword(context: Context): Pair<String, String> {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
val encryptedAccountBase64 = sharedPreferences.getString("account", "") ?: ""
val encryptedPasswordBase64 = sharedPreferences.getString("password", "") ?: ""
val accountIvBase64 = sharedPreferences.getString("account_iv", "") ?: ""
val passwordIvBase64 = sharedPreferences.getString("password_iv", "") ?: ""

if (encryptedAccountBase64.isEmpty() || encryptedPasswordBase64.isEmpty() || accountIvBase64.isEmpty() || passwordIvBase64.isEmpty()) {
return Pair("", "")
}

val encryptedAccount = Base64.getDecoder().decode(encryptedAccountBase64)
val encryptedPassword = Base64.getDecoder().decode(encryptedPasswordBase64)

val accountIv = Base64.getDecoder().decode(accountIvBase64)
val accountCipher = KeyStoreUtils.getDecryptionCipher(accountIv)
val account = String(accountCipher.doFinal(encryptedAccount))

val passwordIv = Base64.getDecoder().decode(passwordIvBase64)
val passwordCipher = KeyStoreUtils.getDecryptionCipher(passwordIv)
val password = String(passwordCipher.doFinal(encryptedPassword))

return Pair(account, password)
}

private fun deleteAccountAndPassword(context: Context) {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
val editor = sharedPreferences.edit()
editor.remove("account").remove("password").remove("account_iv").remove("password_iv").apply()
}

}
4 changes: 3 additions & 1 deletion app/src/main/res/layout/dialog_about.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?colorSurfaceContainerHigh"
Expand All @@ -25,7 +26,8 @@
android:layout_margin="8dp"
android:padding="2dp"
android:src="@drawable/ic_update"
app:tint="?attr/colorOnSecondaryContainer" />
app:tint="?attr/colorOnSecondaryContainer"
tools:ignore="ContentDescription" />

</com.google.android.material.card.MaterialCardView>

Expand Down
6 changes: 3 additions & 3 deletions app/src/main/res/layout/dialog_login.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
android:layout_height="wrap_content"
android:background="?colorSurfaceContainerHigh"
android:orientation="horizontal"
android:paddingHorizontal="25dp"
android:paddingStart="25dp"
android:paddingEnd="27dp"
android:paddingTop="25dp"
android:paddingBottom="0dp">

Expand All @@ -20,10 +21,9 @@
android:id="@+id/global"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:minHeight="0.dp"
android:text="@string/global"
android:textColor="?attr/colorOnSurfaceVariant"
android:textSize="16sp" />
android:textSize="15sp" />

</LinearLayout>
2 changes: 1 addition & 1 deletion app/src/main/res/values-af-rZA/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
<string name="regions_code">Regions code</string>
<string name="login_expired">Login expired</string>
<string name="login_expired_desc">Recommended to login again</string>
<string name="login_expired_dialog">Checked that the login has expired. It is recommended to log in again.</string>
<string name="login_expired_dialog">login expired!</string>
<string name="global">Global account</string>
<string name="download_method">Download method</string>
<string name="download_method_desc">Select the download method</string>
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values-ar-rSA/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
<string name="regions_code">Regions code</string>
<string name="login_expired">Login expired</string>
<string name="login_expired_desc">Recommended to login again</string>
<string name="login_expired_dialog">Checked that the login has expired. It is recommended to log in again.</string>
<string name="login_expired_dialog">login expired!</string>
<string name="global">Global account</string>
<string name="download_method">Download method</string>
<string name="download_method_desc">Select the download method</string>
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values-bo-rBT/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
<string name="regions_code">Regions code</string>
<string name="login_expired">Login expired</string>
<string name="login_expired_desc">Recommended to login again</string>
<string name="login_expired_dialog">Checked that the login has expired. It is recommended to log in again.</string>
<string name="login_expired_dialog">login expired!</string>
<string name="global">Global account</string>
<string name="download_method">Download method</string>
<string name="download_method_desc">Select the download method</string>
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values-ca-rES/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
<string name="regions_code">Regions code</string>
<string name="login_expired">Login expired</string>
<string name="login_expired_desc">Recommended to login again</string>
<string name="login_expired_dialog">Checked that the login has expired. It is recommended to log in again.</string>
<string name="login_expired_dialog">login expired!</string>
<string name="global">Global account</string>
<string name="download_method">Download method</string>
<string name="download_method_desc">Select the download method</string>
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values-cs-rCZ/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
<string name="regions_code">Regions code</string>
<string name="login_expired">Login expired</string>
<string name="login_expired_desc">Recommended to login again</string>
<string name="login_expired_dialog">Checked that the login has expired. It is recommended to log in again.</string>
<string name="login_expired_dialog">login expired!</string>
<string name="global">Global account</string>
<string name="download_method">Download method</string>
<string name="download_method_desc">Select the download method</string>
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values-da-rDK/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
<string name="regions_code">Regions code</string>
<string name="login_expired">Login expired</string>
<string name="login_expired_desc">Recommended to login again</string>
<string name="login_expired_dialog">Checked that the login has expired. It is recommended to log in again.</string>
<string name="login_expired_dialog">login expired!</string>
<string name="global">Global account</string>
<string name="download_method">Download method</string>
<string name="download_method_desc">Select the download method</string>
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values-de-rDE/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
<string name="regions_code">Regions code</string>
<string name="login_expired">Login expired</string>
<string name="login_expired_desc">Recommended to login again</string>
<string name="login_expired_dialog">Checked that the login has expired. It is recommended to log in again.</string>
<string name="login_expired_dialog">login expired!</string>
<string name="global">Global account</string>
<string name="download_method">Download method</string>
<string name="download_method_desc">Select the download method</string>
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values-el-rGR/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
<string name="regions_code">Regions code</string>
<string name="login_expired">Login expired</string>
<string name="login_expired_desc">Recommended to login again</string>
<string name="login_expired_dialog">Checked that the login has expired. It is recommended to log in again.</string>
<string name="login_expired_dialog">login expired!</string>
<string name="global">Global account</string>
<string name="download_method">Download method</string>
<string name="download_method_desc">Select the download method</string>
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values-en-rUS/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
<string name="regions_code">Regions code</string>
<string name="login_expired">Login expired</string>
<string name="login_expired_desc">Recommended to login again</string>
<string name="login_expired_dialog">Checked that the login has expired. It is recommended to log in again.</string>
<string name="login_expired_dialog">login expired!</string>
<string name="global">Global account</string>
<string name="download_method">Download method</string>
<string name="download_method_desc">Select the download method</string>
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values-fi-rFI/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
<string name="regions_code">Regions code</string>
<string name="login_expired">Login expired</string>
<string name="login_expired_desc">Recommended to login again</string>
<string name="login_expired_dialog">Checked that the login has expired. It is recommended to log in again.</string>
<string name="login_expired_dialog">login expired!</string>
<string name="global">Global account</string>
<string name="download_method">Download method</string>
<string name="download_method_desc">Select the download method</string>
Expand Down
Loading

0 comments on commit 87fbb09

Please sign in to comment.