diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 98d0c8135..e4a34b0fa 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -46,6 +46,10 @@
+
+
diff --git a/app/src/main/java/com/aurora/store/data/work/AuthWorker.kt b/app/src/main/java/com/aurora/store/data/work/AuthWorker.kt
index b62cda05d..3ce970815 100644
--- a/app/src/main/java/com/aurora/store/data/work/AuthWorker.kt
+++ b/app/src/main/java/com/aurora/store/data/work/AuthWorker.kt
@@ -1,7 +1,13 @@
package com.aurora.store.data.work
+import android.accounts.Account
+import android.accounts.AccountManager
import android.content.Context
+import android.os.Handler
+import android.os.Looper
+import android.util.Base64
import android.util.Log
+import androidx.core.os.bundleOf
import androidx.hilt.work.HiltWorker
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
@@ -10,8 +16,16 @@ import com.aurora.gplayapi.helpers.AuthHelper
import com.aurora.store.data.model.AccountType
import com.aurora.store.data.providers.AccountProvider
import com.aurora.store.data.providers.AuthProvider
+import com.aurora.store.util.CertUtil.GOOGLE_ACCOUNT_TYPE
+import com.aurora.store.util.CertUtil.GOOGLE_PLAY_AUTH_TOKEN_TYPE
+import com.aurora.store.util.CertUtil.GOOGLE_PLAY_CERT
+import com.aurora.store.util.CertUtil.GOOGLE_PLAY_PACKAGE_NAME
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.take
+import kotlinx.coroutines.runBlocking
/**
* Worker to refresh [AuthData] in background
@@ -26,6 +40,8 @@ open class AuthWorker @AssistedInject constructor(
private val TAG = AuthWorker::class.java.simpleName
+ private val authToken: MutableSharedFlow = MutableSharedFlow(extraBufferCapacity = 1)
+
override suspend fun doWork(): Result {
if (!AccountProvider.isLoggedIn(appContext)) {
Log.i(TAG, "User has logged out!")
@@ -42,14 +58,48 @@ open class AuthWorker @AssistedInject constructor(
val accountType = AccountProvider.getAccountType(appContext)
val authData = when (accountType) {
AccountType.GOOGLE -> {
- authProvider.buildGoogleAuthData(
- AccountProvider.getLoginEmail(appContext)!!,
- AccountProvider.getLoginToken(appContext)!!.first,
- AccountProvider.getLoginToken(appContext)!!.second
- ).getOrThrow()
+ val email = AccountProvider.getLoginEmail(appContext)!!
+ val token = AccountProvider.getLoginToken(appContext)!!.first
+ val tokenType = AccountProvider.getLoginToken(appContext)!!.second
+
+ if (tokenType == AuthHelper.Token.AAS) {
+ Log.i(TAG, "Refreshing AuthData for personal account")
+ authProvider.buildGoogleAuthData(email, token, tokenType).getOrThrow()
+ } else {
+ /*
+ * We are working with AuthToken here. The only scenario when we will have
+ * AuthToken and Google login is when the user used microG to login into
+ * Aurora Store. In this case, we use system's AccountManager to request credentials.
+ */
+ Log.i(TAG, "Refreshing AuthData for personal account using AccountManager")
+ AccountManager.get(appContext)
+ .getAuthToken(
+ Account(email, GOOGLE_ACCOUNT_TYPE),
+ GOOGLE_PLAY_AUTH_TOKEN_TYPE,
+ bundleOf(
+ "overridePackage" to GOOGLE_PLAY_PACKAGE_NAME,
+ "overrideCertificate" to Base64.decode(GOOGLE_PLAY_CERT, Base64.DEFAULT)
+ ),
+ true,
+ {
+ authToken.tryEmit(it.result.getString(AccountManager.KEY_AUTHTOKEN))
+ },
+ Handler(Looper.getMainLooper())
+ )
+ runBlocking {
+ authProvider.buildGoogleAuthData(
+ email,
+ authToken.take(1).first()!!,
+ tokenType
+ ).getOrThrow()
+ }
+ }
}
- AccountType.ANONYMOUS -> authProvider.buildAnonymousAuthData().getOrThrow()
+ AccountType.ANONYMOUS -> {
+ Log.i(TAG, "Refreshing AuthData for anonymous account")
+ authProvider.buildAnonymousAuthData().getOrThrow()
+ }
}
require(verifyAndSaveAuth(authData, accountType) != null)
diff --git a/app/src/main/java/com/aurora/store/util/CertUtil.kt b/app/src/main/java/com/aurora/store/util/CertUtil.kt
index aa8d35d9b..445dc2dbd 100644
--- a/app/src/main/java/com/aurora/store/util/CertUtil.kt
+++ b/app/src/main/java/com/aurora/store/util/CertUtil.kt
@@ -37,6 +37,11 @@ object CertUtil {
private val TAG = "CertUtil"
+ const val GOOGLE_ACCOUNT_TYPE = "com.google"
+ const val GOOGLE_PLAY_AUTH_TOKEN_TYPE = "oauth2:https://www.googleapis.com/auth/googleplay"
+ const val GOOGLE_PLAY_PACKAGE_NAME = "com.android.vending"
+ const val GOOGLE_PLAY_CERT = "MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK"
+
fun isFDroidApp(context: Context, packageName: String): Boolean {
return isInstalledByFDroid(context, packageName) || isSignedByFDroid(context, packageName)
}
diff --git a/app/src/main/java/com/aurora/store/util/PackageUtil.kt b/app/src/main/java/com/aurora/store/util/PackageUtil.kt
index 540f06b23..40e8c618a 100644
--- a/app/src/main/java/com/aurora/store/util/PackageUtil.kt
+++ b/app/src/main/java/com/aurora/store/util/PackageUtil.kt
@@ -46,6 +46,10 @@ object PackageUtil {
private const val TAG = "PackageUtil"
+ private const val PACKAGE_NAME_MICRO_G = "com.google.android.gms"
+ private const val VERSION_CODE_MICRO_G = 240913402
+ private const val VERSION_CODE_MICRO_G_HUAWEI = 240913007
+
fun getAllValidPackages(context: Context): List {
val sharedLibs = context.packageManager.systemSharedLibraryNames ?: emptyArray()
return context.packageManager.getInstalledPackages(PackageManager.GET_META_DATA)
@@ -57,6 +61,14 @@ object PackageUtil {
}
}
+ fun hasSupportedMicroG(context: Context): Boolean {
+ return if (isHuawei) {
+ isInstalled(context, PACKAGE_NAME_MICRO_G, VERSION_CODE_MICRO_G_HUAWEI)
+ } else {
+ isInstalled(context, PACKAGE_NAME_MICRO_G, VERSION_CODE_MICRO_G)
+ }
+ }
+
fun isInstalled(context: Context, packageName: String): Boolean {
return try {
getPackageInfo(context, packageName, PackageManager.GET_META_DATA)
diff --git a/app/src/main/java/com/aurora/store/view/ui/splash/SplashFragment.kt b/app/src/main/java/com/aurora/store/view/ui/splash/SplashFragment.kt
index 5ed9e279f..fbc7a0979 100644
--- a/app/src/main/java/com/aurora/store/view/ui/splash/SplashFragment.kt
+++ b/app/src/main/java/com/aurora/store/view/ui/splash/SplashFragment.kt
@@ -19,19 +19,34 @@
package com.aurora.store.view.ui.splash
+import android.accounts.Account
+import android.accounts.AccountManager
import android.content.Intent
import android.net.UrlQuerySanitizer
import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.util.Base64
+import android.util.Log
import android.view.View
import androidx.core.view.isVisible
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.core.os.bundleOf
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.aurora.extensions.hide
+import com.aurora.extensions.isMAndAbove
import com.aurora.extensions.show
+import com.aurora.gplayapi.helpers.AuthHelper
import com.aurora.store.R
import com.aurora.store.data.model.AuthState
import com.aurora.store.databinding.FragmentSplashBinding
+import com.aurora.store.util.CertUtil.GOOGLE_ACCOUNT_TYPE
+import com.aurora.store.util.CertUtil.GOOGLE_PLAY_AUTH_TOKEN_TYPE
+import com.aurora.store.util.CertUtil.GOOGLE_PLAY_CERT
+import com.aurora.store.util.CertUtil.GOOGLE_PLAY_PACKAGE_NAME
+import com.aurora.store.util.PackageUtil
import com.aurora.store.util.Preferences
import com.aurora.store.util.Preferences.PREFERENCE_DEFAULT_SELECTED_TAB
import com.aurora.store.util.Preferences.PREFERENCE_INTRO
@@ -44,8 +59,20 @@ import kotlinx.coroutines.launch
@AndroidEntryPoint
class SplashFragment : BaseFragment() {
+ private val TAG = SplashFragment::class.java.simpleName
+
private val viewModel: AuthViewModel by activityViewModels()
+ private val startForAccount =
+ registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
+ val accountName = it.data?.getStringExtra(AccountManager.KEY_ACCOUNT_NAME)
+ if (!accountName.isNullOrBlank()) {
+ requestAuthTokenForGoogle(accountName)
+ } else {
+ findNavController().navigate(R.id.googleFragment)
+ }
+ }
+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@@ -176,7 +203,22 @@ class SplashFragment : BaseFragment() {
binding.btnGoogle.addOnClickListener {
if (viewModel.authState.value != AuthState.Fetching) {
binding.btnGoogle.updateProgress(true)
- findNavController().navigate(R.id.googleFragment)
+ if (isMAndAbove && PackageUtil.hasSupportedMicroG(requireContext())) {
+ Log.i(TAG, "Found supported microG, trying to request credentials")
+ val accountIntent = AccountManager.newChooseAccountIntent(
+ null,
+ null,
+ arrayOf(GOOGLE_ACCOUNT_TYPE),
+ null,
+ null,
+ null,
+ null
+ )
+ startForAccount.launch(accountIntent)
+ } else {
+ findNavController().navigate(R.id.googleFragment)
+ }
+
}
}
}
@@ -221,4 +263,30 @@ class SplashFragment : BaseFragment() {
requireArguments().getString("packageName") ?: ""
}
}
+
+ private fun requestAuthTokenForGoogle(accountName: String) {
+ try {
+ AccountManager.get(requireContext())
+ .getAuthToken(
+ Account(accountName, GOOGLE_ACCOUNT_TYPE),
+ GOOGLE_PLAY_AUTH_TOKEN_TYPE,
+ bundleOf(
+ "overridePackage" to GOOGLE_PLAY_PACKAGE_NAME,
+ "overrideCertificate" to Base64.decode(GOOGLE_PLAY_CERT, Base64.DEFAULT)
+ ),
+ requireActivity(),
+ {
+ viewModel.buildGoogleAuthData(
+ accountName,
+ it.result.getString(AccountManager.KEY_AUTHTOKEN)!!,
+ AuthHelper.Token.AUTH
+ )
+ },
+ Handler(Looper.getMainLooper())
+ )
+ } catch (exception: Exception) {
+ Log.e(TAG, "Failed to get authToken for Google login")
+ findNavController().navigate(R.id.googleFragment)
+ }
+ }
}