diff --git a/elixir/apps/web/priv/static/.well-known/assetlinks.json b/elixir/apps/web/priv/static/.well-known/assetlinks.json deleted file mode 100644 index b529b1994..000000000 --- a/elixir/apps/web/priv/static/.well-known/assetlinks.json +++ /dev/null @@ -1,16 +0,0 @@ -[ - { - "relation": [ - "delegate_permission/common.handle_all_urls" - ], - "target": { - "namespace": "android_app", - "package_name": "dev.firezone.android", - "sha256_cert_fingerprints": [ - "82:86:46:E7:B7:FC:BD:01:4E:53:D5:92:E7:07:A6:90:B6:03:07:E5:02:E8:A9:20:EA:EE:54:6B:FA:E6:69:AA", - "CB:FA:24:CE:CE:87:AF:5C:EF:C4:E4:E8:04:BC:C1:B8:34:0B:4E:7E:77:E1:26:80:41:6D:A8:44:56:DF:BA:A5", - "70:FB:26:CB:DB:60:99:ED:E8:D1:11:7F:5F:0E:AC:35:9D:D9:23:14:24:9E:A0:35:C3:FC:5D:22:E2:08:EB:A8" - ] - } - } -] diff --git a/kotlin/android/app/src/main/AndroidManifest.xml b/kotlin/android/app/src/main/AndroidManifest.xml index 0e3a0a1d9..3f751c663 100644 --- a/kotlin/android/app/src/main/AndroidManifest.xml +++ b/kotlin/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ - + @@ -46,44 +47,14 @@ android:exported="false" /> - - - + - - - - - - - - - - - - - - - - - - - + diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/core/data/AuthRepository.kt b/kotlin/android/app/src/main/java/dev/firezone/android/core/data/AuthRepository.kt index 89ea636c3..899013d75 100644 --- a/kotlin/android/app/src/main/java/dev/firezone/android/core/data/AuthRepository.kt +++ b/kotlin/android/app/src/main/java/dev/firezone/android/core/data/AuthRepository.kt @@ -4,5 +4,5 @@ package dev.firezone.android.core.data import kotlinx.coroutines.flow.Flow internal interface AuthRepository { - fun generateCsrfToken(): Flow + fun generateNonce(key: String): Flow } diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/core/data/AuthRepositoryImpl.kt b/kotlin/android/app/src/main/java/dev/firezone/android/core/data/AuthRepositoryImpl.kt index d5c467cf0..a581e9210 100644 --- a/kotlin/android/app/src/main/java/dev/firezone/android/core/data/AuthRepositoryImpl.kt +++ b/kotlin/android/app/src/main/java/dev/firezone/android/core/data/AuthRepositoryImpl.kt @@ -7,7 +7,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn import java.security.SecureRandom -import java.util.Base64 import javax.inject.Inject internal class AuthRepositoryImpl @@ -16,23 +15,22 @@ internal class AuthRepositoryImpl private val coroutineDispatcher: CoroutineDispatcher, private val sharedPreferences: SharedPreferences, ) : AuthRepository { - override fun generateCsrfToken(): Flow = + override fun generateNonce(key: String): Flow = flow { val random = SecureRandom.getInstanceStrong() - val bytes = ByteArray(CSRF_LENGTH) + val bytes = ByteArray(NONCE_LENGTH) random.nextBytes(bytes) - val encodedStr: String = Base64.getEncoder().encodeToString(bytes) + val encodedStr: String = bytes.joinToString("") { "%02x".format(it) } sharedPreferences .edit() - .putString(CSRF_KEY, encodedStr) + .putString(key, encodedStr) .apply() emit(encodedStr) }.flowOn(coroutineDispatcher) companion object { - private const val CSRF_KEY = "csrf" - private const val CSRF_LENGTH = 24 + private const val NONCE_LENGTH = 32 } } diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/core/data/PreferenceRepository.kt b/kotlin/android/app/src/main/java/dev/firezone/android/core/data/PreferenceRepository.kt index a42f2c9e9..d412881ad 100644 --- a/kotlin/android/app/src/main/java/dev/firezone/android/core/data/PreferenceRepository.kt +++ b/kotlin/android/app/src/main/java/dev/firezone/android/core/data/PreferenceRepository.kt @@ -17,7 +17,7 @@ internal interface PreferenceRepository { fun saveToken(value: String): Flow - fun validateCsrfToken(value: String): Flow + fun validateState(value: String): Flow fun clearToken() diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/core/data/PreferenceRepositoryImpl.kt b/kotlin/android/app/src/main/java/dev/firezone/android/core/data/PreferenceRepositoryImpl.kt index 91d859b57..13ab24e09 100644 --- a/kotlin/android/app/src/main/java/dev/firezone/android/core/data/PreferenceRepositoryImpl.kt +++ b/kotlin/android/app/src/main/java/dev/firezone/android/core/data/PreferenceRepositoryImpl.kt @@ -48,24 +48,26 @@ internal class PreferenceRepositoryImpl override fun saveToken(value: String): Flow = flow { + val nonce = sharedPreferences.getString(NONCE_KEY, "").orEmpty() emit( sharedPreferences .edit() - .putString(TOKEN_KEY, value) + .putString(TOKEN_KEY, nonce.plus(value)) .apply(), ) }.flowOn(coroutineDispatcher) - override fun validateCsrfToken(value: String): Flow = + override fun validateState(value: String): Flow = flow { - val token = sharedPreferences.getString(CSRF_KEY, "").orEmpty() - emit(MessageDigest.isEqual(token.toByteArray(), value.toByteArray())) + val state = sharedPreferences.getString(STATE_KEY, "").orEmpty() + emit(MessageDigest.isEqual(state.toByteArray(), value.toByteArray())) }.flowOn(coroutineDispatcher) override fun clearToken() { sharedPreferences.edit().apply { - remove(CSRF_KEY) + remove(NONCE_KEY) remove(TOKEN_KEY) + remove(STATE_KEY) apply() } } @@ -79,6 +81,7 @@ internal class PreferenceRepositoryImpl private const val API_URL_KEY = "apiUrl" private const val LOG_FILTER_KEY = "logFilter" private const val TOKEN_KEY = "token" - private const val CSRF_KEY = "csrf" + private const val NONCE_KEY = "nonce" + private const val STATE_KEY = "state" } } diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/core/domain/auth/GetCsrfTokenUseCase.kt b/kotlin/android/app/src/main/java/dev/firezone/android/core/domain/auth/GetNonceUseCase.kt similarity index 88% rename from kotlin/android/app/src/main/java/dev/firezone/android/core/domain/auth/GetCsrfTokenUseCase.kt rename to kotlin/android/app/src/main/java/dev/firezone/android/core/domain/auth/GetNonceUseCase.kt index 151b25074..5173ad057 100644 --- a/kotlin/android/app/src/main/java/dev/firezone/android/core/domain/auth/GetCsrfTokenUseCase.kt +++ b/kotlin/android/app/src/main/java/dev/firezone/android/core/domain/auth/GetNonceUseCase.kt @@ -5,10 +5,10 @@ import dev.firezone.android.core.data.AuthRepository import kotlinx.coroutines.flow.Flow import javax.inject.Inject -internal class GetCsrfTokenUseCase +internal class GetNonceUseCase @Inject constructor( private val repository: AuthRepository, ) { - operator fun invoke(): Flow = repository.generateCsrfToken() + operator fun invoke(): Flow = repository.generateNonce("nonce") } diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/core/domain/auth/GetStateUseCase.kt b/kotlin/android/app/src/main/java/dev/firezone/android/core/domain/auth/GetStateUseCase.kt new file mode 100644 index 000000000..3a99811be --- /dev/null +++ b/kotlin/android/app/src/main/java/dev/firezone/android/core/domain/auth/GetStateUseCase.kt @@ -0,0 +1,14 @@ +/* Licensed under Apache 2.0 (C) 2023 Firezone, Inc. */ +package dev.firezone.android.core.domain.auth + +import dev.firezone.android.core.data.AuthRepository +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +internal class GetStateUseCase + @Inject + constructor( + private val repository: AuthRepository, + ) { + operator fun invoke(): Flow = repository.generateNonce("state") + } diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/core/domain/preference/ValidateCsrfTokenUseCase.kt b/kotlin/android/app/src/main/java/dev/firezone/android/core/domain/preference/ValidateStateUseCase.kt similarity index 84% rename from kotlin/android/app/src/main/java/dev/firezone/android/core/domain/preference/ValidateCsrfTokenUseCase.kt rename to kotlin/android/app/src/main/java/dev/firezone/android/core/domain/preference/ValidateStateUseCase.kt index 558737403..477e8294b 100644 --- a/kotlin/android/app/src/main/java/dev/firezone/android/core/domain/preference/ValidateCsrfTokenUseCase.kt +++ b/kotlin/android/app/src/main/java/dev/firezone/android/core/domain/preference/ValidateStateUseCase.kt @@ -5,10 +5,10 @@ import dev.firezone.android.core.data.PreferenceRepository import kotlinx.coroutines.flow.Flow import javax.inject.Inject -internal class ValidateCsrfTokenUseCase +internal class ValidateStateUseCase @Inject constructor( private val repository: PreferenceRepository, ) { - operator fun invoke(value: String): Flow = repository.validateCsrfToken(value) + operator fun invoke(value: String): Flow = repository.validateState(value) } diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/features/auth/ui/AuthActivity.kt b/kotlin/android/app/src/main/java/dev/firezone/android/features/auth/ui/AuthActivity.kt index dae1fbeef..f4c50b09a 100644 --- a/kotlin/android/app/src/main/java/dev/firezone/android/features/auth/ui/AuthActivity.kt +++ b/kotlin/android/app/src/main/java/dev/firezone/android/features/auth/ui/AuthActivity.kt @@ -4,17 +4,13 @@ package dev.firezone.android.features.auth.ui import android.content.Intent import android.net.Uri import android.os.Bundle -import android.widget.Toast import androidx.activity.viewModels import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity -import androidx.browser.customtabs.CustomTabsIntent import dagger.hilt.android.AndroidEntryPoint import dev.firezone.android.R import dev.firezone.android.core.presentation.MainActivity import dev.firezone.android.databinding.ActivityAuthBinding -import dev.firezone.android.util.CustomTabsHelper -import java.lang.Exception @AndroidEntryPoint class AuthActivity : AppCompatActivity(R.layout.activity_auth) { @@ -48,30 +44,8 @@ class AuthActivity : AppCompatActivity(R.layout.activity_auth) { } private fun setupWebView(url: String) { - if (CustomTabsHelper.checkIfChromeIsInstalled(this)) { - val intent = CustomTabsIntent.Builder().build() - val packageName = CustomTabsHelper.getPackageNameToUse(this) - if (CustomTabsHelper.checkIfChromeAppIsDefault()) { - if (packageName != null) { - intent.intent.setPackage(packageName) - } - } else { - intent.intent.setPackage(CustomTabsHelper.STABLE_PACKAGE) - } - - try { - intent.launchUrl(this@AuthActivity, Uri.parse(url)) - } catch (e: Exception) { - showChromeAppRequiredError() - } - } else { - showChromeAppRequiredError() - } - } - - private fun showChromeAppRequiredError() { - Toast.makeText(this, getString(R.string.signing_in_requires_chrome_browser), Toast.LENGTH_LONG).show() - navigateToSignIn() + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + startActivity(intent) } private fun navigateToSignIn() { diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/features/auth/ui/AuthViewModel.kt b/kotlin/android/app/src/main/java/dev/firezone/android/features/auth/ui/AuthViewModel.kt index 60df889ec..abff60179 100644 --- a/kotlin/android/app/src/main/java/dev/firezone/android/features/auth/ui/AuthViewModel.kt +++ b/kotlin/android/app/src/main/java/dev/firezone/android/features/auth/ui/AuthViewModel.kt @@ -6,7 +6,8 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import dev.firezone.android.core.domain.auth.GetCsrfTokenUseCase +import dev.firezone.android.core.domain.auth.GetNonceUseCase +import dev.firezone.android.core.domain.auth.GetStateUseCase import dev.firezone.android.core.domain.preference.GetConfigUseCase import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch @@ -18,7 +19,8 @@ internal class AuthViewModel @Inject constructor( private val getConfigUseCase: GetConfigUseCase, - private val getCsrfTokenUseCase: GetCsrfTokenUseCase, + private val getStateUseCase: GetStateUseCase, + private val getNonceUseCase: GetNonceUseCase, ) : ViewModel() { private val actionMutableLiveData = MutableLiveData() val actionLiveData: LiveData = actionMutableLiveData @@ -32,9 +34,13 @@ internal class AuthViewModel getConfigUseCase() .firstOrNull() ?: throw Exception("config cannot be null") - val csrfToken = - getCsrfTokenUseCase() - .firstOrNull() ?: throw Exception("csrfToken cannot be null") + val state = + getStateUseCase() + .firstOrNull() ?: throw Exception("state cannot be null") + + val nonce = + getNonceUseCase() + .firstOrNull() ?: throw Exception("nonce cannot be null") actionMutableLiveData.postValue( if (authFlowLaunched || config.token != null) { @@ -44,7 +50,7 @@ internal class AuthViewModel ViewAction.LaunchAuthFlow( url = "${config.authBaseUrl}" + - "?client_csrf_token=$csrfToken&client_platform=android", + "?state=$state&nonce=$nonce&as=client", ) }, ) diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/features/applink/ui/AppLinkHandlerActivity.kt b/kotlin/android/app/src/main/java/dev/firezone/android/features/customuri/ui/CustomUriHandlerActivity.kt similarity index 63% rename from kotlin/android/app/src/main/java/dev/firezone/android/features/applink/ui/AppLinkHandlerActivity.kt rename to kotlin/android/app/src/main/java/dev/firezone/android/features/customuri/ui/CustomUriHandlerActivity.kt index ac3c8d30e..f62436700 100644 --- a/kotlin/android/app/src/main/java/dev/firezone/android/features/applink/ui/AppLinkHandlerActivity.kt +++ b/kotlin/android/app/src/main/java/dev/firezone/android/features/customuri/ui/CustomUriHandlerActivity.kt @@ -1,5 +1,5 @@ /* Licensed under Apache 2.0 (C) 2023 Firezone, Inc. */ -package dev.firezone.android.features.applink.ui +package dev.firezone.android.features.customuri.ui import android.content.Intent import android.os.Bundle @@ -9,33 +9,33 @@ import androidx.appcompat.app.AppCompatActivity import dagger.hilt.android.AndroidEntryPoint import dev.firezone.android.R import dev.firezone.android.core.presentation.MainActivity -import dev.firezone.android.databinding.ActivityAppLinkHandlerBinding +import dev.firezone.android.databinding.ActivityCustomUriHandlerBinding @AndroidEntryPoint -class AppLinkHandlerActivity : AppCompatActivity(R.layout.activity_app_link_handler) { - private lateinit var binding: ActivityAppLinkHandlerBinding - private val viewModel: AppLinkViewModel by viewModels() +class CustomUriHandlerActivity : AppCompatActivity(R.layout.activity_custom_uri_handler) { + private lateinit var binding: ActivityCustomUriHandlerBinding + private val viewModel: CustomUriViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - binding = ActivityAppLinkHandlerBinding.inflate(layoutInflater) + binding = ActivityCustomUriHandlerBinding.inflate(layoutInflater) setupActionObservers() - viewModel.parseAppLink(intent) + viewModel.parseCustomUri(intent) } private fun setupActionObservers() { viewModel.actionLiveData.observe(this) { action -> when (action) { - AppLinkViewModel.ViewAction.AuthFlowComplete -> { + CustomUriViewModel.ViewAction.AuthFlowComplete -> { startActivity( - Intent(this@AppLinkHandlerActivity, MainActivity::class.java).apply { + Intent(this@CustomUriHandlerActivity, MainActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP }, ) finish() } - AppLinkViewModel.ViewAction.ShowError -> showError() + CustomUriViewModel.ViewAction.ShowError -> showError() } } } @@ -47,7 +47,7 @@ class AppLinkHandlerActivity : AppCompatActivity(R.layout.activity_app_link_hand .setPositiveButton( R.string.error_dialog_button_text, ) { _, _ -> - this@AppLinkHandlerActivity.finish() + this@CustomUriHandlerActivity.finish() } .setIcon(R.drawable.ic_firezone_logo) .show() diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/features/applink/ui/AppLinkViewModel.kt b/kotlin/android/app/src/main/java/dev/firezone/android/features/customuri/ui/CustomUriViewModel.kt similarity index 52% rename from kotlin/android/app/src/main/java/dev/firezone/android/features/applink/ui/AppLinkViewModel.kt rename to kotlin/android/app/src/main/java/dev/firezone/android/features/customuri/ui/CustomUriViewModel.kt index 1ca4629ea..42a58a6b8 100644 --- a/kotlin/android/app/src/main/java/dev/firezone/android/features/applink/ui/AppLinkViewModel.kt +++ b/kotlin/android/app/src/main/java/dev/firezone/android/features/customuri/ui/CustomUriViewModel.kt @@ -1,5 +1,5 @@ /* Licensed under Apache 2.0 (C) 2023 Firezone, Inc. */ -package dev.firezone.android.features.applink.ui +package dev.firezone.android.features.customuri.ui import android.content.Intent import android.util.Log @@ -9,42 +9,43 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import dev.firezone.android.core.domain.preference.SaveTokenUseCase -import dev.firezone.android.core.domain.preference.ValidateCsrfTokenUseCase +import dev.firezone.android.core.domain.preference.ValidateStateUseCase import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel -internal class AppLinkViewModel +internal class CustomUriViewModel @Inject constructor( - private val validateCsrfTokenUseCase: ValidateCsrfTokenUseCase, + private val validateStateUseCase: ValidateStateUseCase, private val saveTokenUseCase: SaveTokenUseCase, ) : ViewModel() { private val actionMutableLiveData = MutableLiveData() val actionLiveData: LiveData = actionMutableLiveData - fun parseAppLink(intent: Intent) { - Log.d("AppLinkViewModel", "Parsing app link...") + fun parseCustomUri(intent: Intent) { + Log.d("CustomUriViewModel", "Parsing app link...") viewModelScope.launch { - Log.d("AppLinkViewModel", "viewmodelScope.launch") + Log.d("CustomUriViewModel", "viewmodelScope.launch") when (intent.data?.lastPathSegment) { PATH_CALLBACK -> { - Log.d("AppLinkViewModel", "PATH_CALLBACK") - intent.data?.getQueryParameter(QUERY_CLIENT_CSRF_TOKEN)?.let { csrfToken -> - Log.d("AppLinkViewModel", "csrfToken: $csrfToken") - if (validateCsrfTokenUseCase(csrfToken).firstOrNull() == true) { - Log.d("AppLinkViewModel", "Valid CSRF token. Continuing to save token...") + Log.d("CustomUriViewModel", "PATH_CALLBACK") + intent.data?.getQueryParameter(QUERY_CLIENT_STATE)?.let { state -> + Log.d("CustomUriViewModel", "state: $state") + if (validateStateUseCase(state).firstOrNull() == true) { + Log.d("CustomUriViewModel", "Valid state parameter. Continuing to save state...") } else { - Log.d("AppLinkViewModel", "Invalid CSRF token! Continuing to save token anyway...") + Log.d("CustomUriViewModel", "Invalid state parameter! Ignoring...") + actionMutableLiveData.postValue(ViewAction.ShowError) } - intent.data?.getQueryParameter(QUERY_CLIENT_AUTH_TOKEN)?.let { token -> - if (token.isNotBlank()) { - Log.d("AppLinkViewModel", "Found valid auth token in response") - saveTokenUseCase(token).collect() + intent.data?.getQueryParameter(QUERY_CLIENT_AUTH_FRAGMENT)?.let { fragment -> + if (fragment.isNotBlank()) { + Log.d("CustomUriViewModel", "Found valid auth fragment in response") + saveTokenUseCase(fragment).collect() } else { - Log.d("AppLinkViewModel", "Didn't find auth token in response!") + Log.d("CustomUriViewModel", "Didn't find auth fragment in response!") } } @@ -52,16 +53,16 @@ internal class AppLinkViewModel } } else -> { - Log.d("AppLinkViewModel", "Unknown path segment: ${intent.data?.lastPathSegment}") + Log.d("CustomUriViewModel", "Unknown path segment: ${intent.data?.lastPathSegment}") } } } } companion object { - private const val PATH_CALLBACK = "handle_client_auth_callback" - private const val QUERY_CLIENT_CSRF_TOKEN = "client_csrf_token" - private const val QUERY_CLIENT_AUTH_TOKEN = "client_auth_token" + private const val PATH_CALLBACK = "handle_client_sign_in_callback" + private const val QUERY_CLIENT_STATE = "state" + private const val QUERY_CLIENT_AUTH_FRAGMENT = "fragment" private const val QUERY_ACTOR_NAME = "actor_name" private const val QUERY_IDENTITY_PROVIDER_IDENTIFIER = "identity_provider_identifier" } diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/util/CustomTabsHelper.kt b/kotlin/android/app/src/main/java/dev/firezone/android/util/CustomTabsHelper.kt deleted file mode 100644 index 06527c514..000000000 --- a/kotlin/android/app/src/main/java/dev/firezone/android/util/CustomTabsHelper.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* Licensed under Apache 2.0 (C) 2015 Firezone, Inc. */ -package dev.firezone.android.util - -import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager -import android.net.Uri -import android.os.Build - -class CustomTabsHelper { - companion object { - val STABLE_PACKAGE = "com.android.chrome" - val BETA_PACKAGE = "com.chrome.beta" - val DEV_PACKAGE = "com.chrome.dev" - val LOCAL_PACKAGE = "com.google.android.apps.chrome" - val ACTION_CUSTOM_TABS_CONNECTION = "android.support.customtabs.action.CustomTabsService" - - private var sPackageNameToUse: String? = null - - fun getPackageNameToUse(context: Context): String? { - if (sPackageNameToUse != null) return sPackageNameToUse - val pm = context.getPackageManager() - val activityIntent = Intent(Intent.ACTION_VIEW, Uri.parse("http://www.example.com")) - val defaultViewHandlerInfo = pm.resolveActivity(activityIntent, 0) - var defaultViewHandlerPackageName: String? = null - if (defaultViewHandlerInfo != null) { - defaultViewHandlerPackageName = defaultViewHandlerInfo.activityInfo.packageName - } - val resolvedActivityList = pm.queryIntentActivities(activityIntent, 0) - val packagesSupportingCustomTabs: MutableList = ArrayList() - for (info in resolvedActivityList) { - val serviceIntent = Intent() - serviceIntent.action = ACTION_CUSTOM_TABS_CONNECTION - serviceIntent.setPackage(info.activityInfo.packageName) - if (pm.resolveService(serviceIntent, 0) != null) { - packagesSupportingCustomTabs.add(info.activityInfo.packageName) - } - } - if (packagesSupportingCustomTabs.size == 1) { - sPackageNameToUse = packagesSupportingCustomTabs.get(0) - } else if (packagesSupportingCustomTabs.contains(STABLE_PACKAGE)) { - sPackageNameToUse = STABLE_PACKAGE - } else if (packagesSupportingCustomTabs.contains(BETA_PACKAGE)) { - sPackageNameToUse = BETA_PACKAGE - } else if (packagesSupportingCustomTabs.contains(DEV_PACKAGE)) { - sPackageNameToUse = DEV_PACKAGE - } else if (packagesSupportingCustomTabs.contains(LOCAL_PACKAGE)) { - sPackageNameToUse = LOCAL_PACKAGE - } - return sPackageNameToUse - } - - fun checkIfChromeAppIsDefault() = - sPackageNameToUse == STABLE_PACKAGE || - sPackageNameToUse == BETA_PACKAGE || - sPackageNameToUse == DEV_PACKAGE || - sPackageNameToUse == LOCAL_PACKAGE - - fun checkIfChromeIsInstalled(context: Context): Boolean = - try { - val info = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - context.packageManager.getPackageInfo(STABLE_PACKAGE, PackageManager.PackageInfoFlags.of(0L)) - } else { - @Suppress("DEPRECATION") - context.packageManager.getPackageInfo(STABLE_PACKAGE, 0) - } - info.applicationInfo.enabled - } catch (e: PackageManager.NameNotFoundException) { - false - } - } -} diff --git a/kotlin/android/app/src/main/res/layout/activity_app_link_handler.xml b/kotlin/android/app/src/main/res/layout/activity_custom_uri_handler.xml similarity index 91% rename from kotlin/android/app/src/main/res/layout/activity_app_link_handler.xml rename to kotlin/android/app/src/main/res/layout/activity_custom_uri_handler.xml index 1a7a60f98..412d52a84 100644 --- a/kotlin/android/app/src/main/res/layout/activity_app_link_handler.xml +++ b/kotlin/android/app/src/main/res/layout/activity_custom_uri_handler.xml @@ -5,7 +5,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".features.applink.ui.AppLinkHandlerActivity"> + tools:context=".features.customuri.ui.CustomUriHandlerActivity">