Add support for proxies - 2/3

This commit is contained in:
Rahul Patel
2023-10-22 14:27:13 +05:30
parent 687c14c394
commit f19631ad6a
36 changed files with 226 additions and 44 deletions

View File

@@ -43,6 +43,7 @@ object Constants {
const val URL_DISPENSER = "https://auroraoss.com/api/auth"
const val PLAY_QUERY_URL = "https://play.google.com/store/search?q="
const val ANDROID_CONNECTIVITY_URL = "http://connectivitycheck.android.com/generate_204"
//ACCOUNTS
const val ACCOUNT_SIGNED_IN = "ACCOUNT_SIGNED_IN"

View File

@@ -23,6 +23,7 @@ import android.os.Handler
import android.os.Looper
fun runAsync(action: () -> Unit) = Thread(Runnable(action)).start()
fun runAsyncResult(action: () -> Boolean) = Thread { action() }.apply { start() }
fun runOnUiThread(action: () -> Unit) {
when {

View File

@@ -0,0 +1,9 @@
package com.aurora.store.data.model
data class ProxyInfo(
var protocol: String,
var host: String,
var port: Int,
var proxyUser: String?,
var proxyPassword: String?
)

View File

@@ -19,11 +19,32 @@
package com.aurora.store.data.network
import com.aurora.gplayapi.network.IHttpClient
import android.content.Context
import com.aurora.store.data.model.ProxyInfo
import com.aurora.store.util.Log
import com.aurora.store.util.Preferences
import com.google.gson.Gson
object HttpClient {
fun getPreferredClient(context: Context): IProxyHttpClient {
val proxyEnabled = Preferences.getBoolean(
context,
Preferences.PREFERENCE_PROXY_ENABLED
)
fun getPreferredClient(): IHttpClient {
return OkHttpClient
return if (proxyEnabled) {
val proxyInfoString = Preferences.getString(context, Preferences.PREFERENCE_PROXY_INFO)
val proxyInfo = Gson().fromJson(proxyInfoString, ProxyInfo::class.java)
if (proxyInfo != null) {
OkHttpClient.setProxy(proxyInfo)
} else {
Log.e("Proxy info is unavailable, using default client")
OkHttpClient
}
} else {
Log.i("Proxy is disabled")
OkHttpClient
}
}
}

View File

@@ -1,9 +1,10 @@
package com.aurora.store.data.network
import com.aurora.gplayapi.network.IHttpClient
import com.aurora.store.data.model.ProxyInfo
import java.net.Proxy
interface IProxyHttpClient : IHttpClient {
@Throws(UnsupportedOperationException::class)
fun setProxy(proxy: Proxy, proxyUser: String?, proxyPassword: String?): IHttpClient
fun setProxy(proxyInfo: ProxyInfo): IHttpClient
}

View File

@@ -21,6 +21,7 @@ package com.aurora.store.data.network
import com.aurora.gplayapi.data.models.PlayResponse
import com.aurora.store.BuildConfig
import com.aurora.store.data.model.ProxyInfo
import com.aurora.store.util.Log
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -31,6 +32,7 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.IOException
import java.net.InetSocketAddress
import java.net.Proxy
import java.util.concurrent.TimeUnit
@@ -52,13 +54,29 @@ object OkHttpClient : IProxyHttpClient {
.followRedirects(true)
.followSslRedirects(true)
override fun setProxy(
proxy: Proxy, proxyUser: String?, proxyPassword: String?
): IProxyHttpClient {
if (proxyUser != null) {
okHttpClientBuilder.proxyAuthenticator { route, response ->
val credential = Credentials.basic(proxyUser, proxyPassword.orEmpty())
response.request.newBuilder().header("Proxy-Authorization", credential).build()
override fun setProxy(proxyInfo: ProxyInfo): OkHttpClient {
val proxy = Proxy(
if (proxyInfo.protocol == "socks") Proxy.Type.SOCKS else Proxy.Type.HTTP,
InetSocketAddress.createUnresolved(
proxyInfo.host,
proxyInfo.port
)
)
val proxyUser = proxyInfo.proxyUser
val proxyPassword = proxyInfo.proxyPassword
if (proxyUser != null && proxyPassword != null) {
okHttpClientBuilder.proxyAuthenticator { _, response ->
if (response.request.header("Proxy-Authorization") != null) {
return@proxyAuthenticator null
}
val credential = Credentials.basic(proxyUser, proxyPassword)
response.request
.newBuilder()
.header("Proxy-Authorization", credential)
.build()
}
}

View File

@@ -220,7 +220,7 @@ class UpdateService: LifecycleService() {
}
EventBus.getDefault().register(this)
authData = AuthProvider.with(this).getAuthData()
purchaseHelper = PurchaseHelper(authData).using(HttpClient.getPreferredClient())
purchaseHelper = PurchaseHelper(authData).using(HttpClient.getPreferredClient(this))
downloadManager = DownloadManager.with(this)
fetch = downloadManager.fetch
fetchListener = object : FetchGroupListener {

View File

@@ -104,7 +104,7 @@ class UpdateWorker(private val appContext: Context, workerParams: WorkerParamete
Log.i("Checking for app updates")
val appDetailsHelper = AppDetailsHelper(authData)
.using(HttpClient.getPreferredClient())
.using(HttpClient.getPreferredClient(appContext))
val isGoogleFilterEnabled = Preferences.getBoolean(
appContext,
@@ -153,7 +153,7 @@ class UpdateWorker(private val appContext: Context, workerParams: WorkerParamete
private fun isValid(authData: AuthData): Boolean {
return try {
AuthValidator(authData)
.using(HttpClient.getPreferredClient())
.using(HttpClient.getPreferredClient(appContext))
.isValid()
} catch (e: Exception) {
false

View File

@@ -22,6 +22,7 @@ package com.aurora.store.util
import android.content.Context
import com.aurora.extensions.isSAndAbove
import com.aurora.store.R
import com.aurora.store.data.model.ProxyInfo
import java.text.DecimalFormat
import java.util.Locale
import kotlin.math.ln
@@ -82,9 +83,11 @@ object CommonUtil {
hours > 0 -> {
context.getString(R.string.download_eta_hrs, hours, minutes, seconds)
}
minutes > 0 -> {
context.getString(R.string.download_eta_min, minutes, seconds)
}
else -> {
context.getString(R.string.download_eta_sec, seconds)
}
@@ -126,9 +129,11 @@ object CommonUtil {
mb >= 1 -> {
context.getString(R.string.download_speed_mb, decimalFormat.format(mb))
}
kb >= 1 -> {
context.getString(R.string.download_speed_kb, decimalFormat.format(kb))
}
else -> {
context.getString(R.string.download_speed_bytes, downloadedBytesPerSecond)
}
@@ -179,4 +184,29 @@ object CommonUtil {
else -> if (isSAndAbove()) R.style.Accent00 else R.style.Accent01
}
}
fun parseProxyUrl(proxyUrl: String): ProxyInfo? {
val pattern = """^(https?|socks)://(?:([^\s:@]+):([^\s:@]+)@)?([^\s:@]+):(\d+)$""".toRegex()
val match = pattern.find(proxyUrl)
return when {
match != null -> {
val protocol = match.groupValues[1].toUpperCase()
val username = match.groupValues[2]
val password = match.groupValues[3]
val url = match.groupValues[4]
val port = match.groupValues[5]
ProxyInfo(
protocol,
url,
port.toInt(),
username,
password
)
}
else -> null
}
}
}

View File

@@ -56,6 +56,9 @@ object Preferences {
const val PREFERENCE_TOS_READ = "PREFERENCE_TOS_READ"
const val PREFERENCE_INSECURE_ANONYMOUS = "PREFERENCE_INSECURE_ANONYMOUS"
const val PREFERENCE_PROXY_URL = "PREFERENCE_PROXY_URL"
const val PREFERENCE_PROXY_INFO = "PREFERENCE_PROXY_INFO"
const val PREFERENCE_PROXY_ENABLED = "PREFERENCE_PROXY_ENABLED"
const val PREFERENCE_UPDATES_EXTENDED = "PREFERENCE_UPDATES_EXTENDED"
const val PREFERENCE_UPDATES_CHECK = "PREFERENCE_UPDATES_CHECK"

View File

@@ -1081,7 +1081,7 @@ class AppDetailsFragment : BaseFragment(R.layout.fragment_details) {
}
private fun inflateAppPrivacy(app: App) {
viewModel.fetchAppReport(app.packageName)
viewModel.fetchAppReport(requireContext(), app.packageName)
}
private fun inflateAppDevInfo(B: LayoutDetailsDevBinding, app: App) {

View File

@@ -48,6 +48,7 @@ import com.aurora.store.util.Preferences.PREFERENCE_FOR_YOU
import com.aurora.store.util.Preferences.PREFERENCE_INSECURE_ANONYMOUS
import com.aurora.store.util.Preferences.PREFERENCE_INSTALLER_ID
import com.aurora.store.util.Preferences.PREFERENCE_INTRO
import com.aurora.store.util.Preferences.PREFERENCE_PROXY_ENABLED
import com.aurora.store.util.Preferences.PREFERENCE_SIMILAR
import com.aurora.store.util.Preferences.PREFERENCE_THEME_ACCENT
import com.aurora.store.util.Preferences.PREFERENCE_THEME_TYPE
@@ -165,6 +166,7 @@ class OnboardingFragment : Fragment(R.layout.fragment_onboarding) {
/*Network*/
save(PREFERENCE_INSECURE_ANONYMOUS, false)
save(PREFERENCE_PROXY_ENABLED, false)
/*Customization*/
save(PREFERENCE_THEME_TYPE, 0)

View File

@@ -25,16 +25,82 @@ import androidx.appcompat.widget.Toolbar
import androidx.navigation.fragment.findNavController
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.aurora.Constants
import com.aurora.extensions.runAsync
import com.aurora.extensions.runOnUiThread
import com.aurora.extensions.toast
import com.aurora.store.R
import com.aurora.store.data.network.HttpClient
import com.aurora.store.util.CommonUtil
import com.aurora.store.util.Preferences
import com.aurora.store.util.save
import com.google.gson.Gson
class NetworkPreference : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences_network, rootKey)
val proxyUrl = Preferences.getString(requireContext(), Preferences.PREFERENCE_PROXY_URL, "")
val proxyInfo =
Preferences.getString(requireContext(), Preferences.PREFERENCE_PROXY_INFO, "{}")
val preferenceProxyUrl: Preference? = findPreference(Preferences.PREFERENCE_PROXY_URL)
preferenceProxyUrl?.summary = proxyUrl
preferenceProxyUrl?.let {
it.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { preference, newValue ->
val newProxyUrl = newValue.toString()
val newProxyInfo = CommonUtil.parseProxyUrl(newProxyUrl)
if (newProxyInfo == null) {
runOnUiThread {
requireContext().toast(getString(R.string.toast_proxy_invalid))
}
false
} else {
runAsync {
kotlin.runCatching {
val result = HttpClient
.getPreferredClient(requireContext())
.setProxy(newProxyInfo)
.get(
Constants.ANDROID_CONNECTIVITY_URL,
mapOf()
)
runOnUiThread {
if (result.code == 204) {
requireContext().toast(getString(R.string.toast_proxy_success))
save(Preferences.PREFERENCE_PROXY_URL, newProxyUrl)
save(
Preferences.PREFERENCE_PROXY_INFO,
Gson().toJson(newProxyInfo)
)
preference.summary = newProxyUrl
} else {
throw Exception("Failed to set proxy")
}
}
}.onFailure {
runOnUiThread {
requireContext().toast(getText(R.string.toast_proxy_failed))
save(Preferences.PREFERENCE_PROXY_URL, proxyUrl)
save(
Preferences.PREFERENCE_PROXY_INFO,
proxyInfo
)
}
}
}
true
}
}
}
val insecureAnonymous: Preference? =
findPreference(Preferences.PREFERENCE_INSECURE_ANONYMOUS)
@@ -56,4 +122,4 @@ class NetworkPreference : PreferenceFragmentCompat() {
setNavigationOnClickListener { findNavController().navigateUp() }
}
}
}
}

View File

@@ -32,7 +32,7 @@ import java.lang.reflect.Modifier
abstract class BaseAndroidViewModel(application: Application) : AndroidViewModel(application) {
val responseCode = HttpClient.getPreferredClient().responseCode
val responseCode = HttpClient.getPreferredClient(application).responseCode
protected val gson: Gson = GsonBuilder()
.excludeFieldsWithModifiers(Modifier.TRANSIENT, Modifier.STATIC)

View File

@@ -29,7 +29,7 @@ class MainViewModel : ViewModel() {
try {
val gson: Gson =
GsonBuilder().excludeFieldsWithModifiers(Modifier.TRANSIENT).create()
val response = HttpClient.getPreferredClient().get(Constants.UPDATE_URL, mapOf())
val response = HttpClient.getPreferredClient(context).get(Constants.UPDATE_URL, mapOf())
val selfUpdate =
gson.fromJson(String(response.responseBytes), SelfUpdate::class.java)

View File

@@ -37,7 +37,7 @@ abstract class BaseAppsViewModel(application: Application) : BaseAndroidViewMode
.getAuthData()
private val appDetailsHelper = AppDetailsHelper(authData)
.using(HttpClient.getPreferredClient())
.using(HttpClient.getPreferredClient(application))
var blacklistProvider = BlacklistProvider
.with(application)

View File

@@ -37,7 +37,7 @@ class LibraryAppsViewModel(application: Application) : BaseAndroidViewModel(appl
private val authData: AuthData = AuthProvider.with(application).getAuthData()
private val clusterHelper: ClusterHelper =
ClusterHelper(authData).using(HttpClient.getPreferredClient())
ClusterHelper(authData).using(HttpClient.getPreferredClient(application))
val liveData: MutableLiveData<StreamCluster> = MutableLiveData()
var streamCluster: StreamCluster = StreamCluster()

View File

@@ -41,7 +41,7 @@ class PurchasedViewModel(application: Application) : BaseAndroidViewModel(applic
private val authData = AuthProvider.with(application).getAuthData()
private val purchaseHelper = PurchaseHelper(authData).using(HttpClient.getPreferredClient())
private val purchaseHelper = PurchaseHelper(authData).using(HttpClient.getPreferredClient(application))
private var appList: MutableList<App> = mutableListOf()

View File

@@ -121,7 +121,7 @@ class AuthViewModel(application: Application) : BaseAndroidViewModel(application
properties = spoofProvider.getSpoofDeviceProperties()
val playResponse = HttpClient
.getPreferredClient()
.getPreferredClient(getApplication())
.postAuth(
Constants.URL_DISPENSER,
gson.toJson(properties).toByteArray()
@@ -157,7 +157,7 @@ class AuthViewModel(application: Application) : BaseAndroidViewModel(application
properties = spoofProvider.getSpoofDeviceProperties()
val playResponse = HttpClient
.getPreferredClient()
.getPreferredClient(getApplication())
.getAuth(
Constants.URL_DISPENSER
)
@@ -275,7 +275,7 @@ class AuthViewModel(application: Application) : BaseAndroidViewModel(application
private fun isValid(authData: AuthData): Boolean {
return try {
AuthValidator(authData)
.using(HttpClient.getPreferredClient())
.using(HttpClient.getPreferredClient(getApplication()))
.isValid()
} catch (e: Exception) {
false

View File

@@ -38,7 +38,7 @@ class ExpandedStreamBrowseViewModel(application: Application) : BaseAndroidViewM
private val authData: AuthData = AuthProvider.with(application).getAuthData()
private val streamHelper: ExpandedBrowseHelper = ExpandedBrowseHelper(authData)
.using(HttpClient.getPreferredClient())
.using(HttpClient.getPreferredClient(application))
val liveData: MutableLiveData<StreamCluster> = MutableLiveData()
var streamCluster: StreamCluster = StreamCluster()

View File

@@ -38,7 +38,7 @@ class StreamBrowseViewModel(application: Application) : BaseAndroidViewModel(app
private val authData: AuthData = AuthProvider.with(application).getAuthData()
private val streamHelper: StreamHelper = StreamHelper(authData)
.using(HttpClient.getPreferredClient())
.using(HttpClient.getPreferredClient(application))
val liveData: MutableLiveData<StreamCluster> = MutableLiveData()
var streamCluster: StreamCluster = StreamCluster()

View File

@@ -39,7 +39,7 @@ abstract class BaseCategoryViewModel(application: Application) : BaseAndroidView
.getAuthData()
private val streamHelper: CategoryHelper = CategoryHelper(authData)
.using(HttpClient.getPreferredClient())
.using(HttpClient.getPreferredClient(application))
val liveData: MutableLiveData<List<Category>> = MutableLiveData()

View File

@@ -49,7 +49,7 @@ class AppDetailsViewModel : ViewModel() {
try {
val authData = AuthProvider.with(context).getAuthData()
_app.emit(
AppDetailsHelper(authData).using(HttpClient.getPreferredClient())
AppDetailsHelper(authData).using(HttpClient.getPreferredClient(context))
.getAppByPackageName(packageName)
)
} catch (exception: Exception) {
@@ -63,7 +63,7 @@ class AppDetailsViewModel : ViewModel() {
viewModelScope.launch(Dispatchers.IO) {
try {
val authData = AuthProvider.with(context).getAuthData()
_reviews.emit(ReviewsHelper(authData).using(HttpClient.getPreferredClient())
_reviews.emit(ReviewsHelper(authData).using(HttpClient.getPreferredClient(context))
.getReviewSummary(packageName))
} catch (exception: Exception) {
Log.e(TAG, "Failed to fetch app reviews", exception)
@@ -77,7 +77,7 @@ class AppDetailsViewModel : ViewModel() {
try {
val authData = AuthProvider.with(context).getAuthData()
_userReview.emit(ReviewsHelper(authData)
.using(HttpClient.getPreferredClient())
.using(HttpClient.getPreferredClient(context))
.addOrEditReview(
packageName,
review.title,
@@ -93,7 +93,7 @@ class AppDetailsViewModel : ViewModel() {
}
fun fetchAppReport(packageName: String) {
fun fetchAppReport(context: Context,packageName: String) {
viewModelScope.launch(Dispatchers.IO) {
try {
val headers: MutableMap<String, String> = mutableMapOf()
@@ -102,7 +102,7 @@ class AppDetailsViewModel : ViewModel() {
headers["Authorization"] = exodusApiKey
val url = exodusBaseUrl + packageName
val playResponse = HttpClient.getPreferredClient().get(url, headers)
val playResponse = HttpClient.getPreferredClient(context).get(url, headers)
_report.emit(parseResponse(String(playResponse.responseBytes), packageName)[0])
} catch (exception: Exception) {

View File

@@ -40,7 +40,7 @@ import kotlinx.coroutines.supervisorScope
class DetailsClusterViewModel(application: Application) : BaseAndroidViewModel(application) {
private var authData: AuthData = AuthProvider.with(application).getAuthData()
private var appDetailsHelper = AppDetailsHelper(authData).using(HttpClient.getPreferredClient())
private var appDetailsHelper = AppDetailsHelper(authData).using(HttpClient.getPreferredClient(application))
private var streamHelper = StreamHelper(authData)
val liveData: MutableLiveData<ViewState> = MutableLiveData()

View File

@@ -41,7 +41,7 @@ import kotlinx.coroutines.supervisorScope
class DevProfileViewModel(application: Application) : BaseAndroidViewModel(application) {
private var authData: AuthData = AuthProvider.with(application).getAuthData()
private var appDetailsHelper = AppDetailsHelper(authData).using(HttpClient.getPreferredClient())
private var appDetailsHelper = AppDetailsHelper(authData).using(HttpClient.getPreferredClient(application))
private var streamHelper = StreamHelper(authData)
val liveData: MutableLiveData<ViewState> = MutableLiveData()

View File

@@ -40,7 +40,7 @@ open class BaseEditorChoiceViewModel(application: Application) : BaseAndroidView
.getAuthData()
private val streamHelper: StreamHelper = StreamHelper(authData)
.using(HttpClient.getPreferredClient())
.using(HttpClient.getPreferredClient(application))
lateinit var category: StreamHelper.Category

View File

@@ -37,7 +37,7 @@ class EditorBrowseViewModel(application: Application) : BaseAndroidViewModel(app
private val authData: AuthData = AuthProvider.with(application).getAuthData()
private val streamHelper: StreamHelper = StreamHelper(authData)
.using(HttpClient.getPreferredClient())
.using(HttpClient.getPreferredClient(application))
val liveData: MutableLiveData<MutableList<App>> = MutableLiveData()
val appList: MutableList<App> = mutableListOf()

View File

@@ -39,8 +39,9 @@ import kotlinx.coroutines.supervisorScope
abstract class BaseClusterViewModel(application: Application) : BaseAndroidViewModel(application) {
var authData: AuthData = AuthProvider.with(application).getAuthData()
var streamHelper: StreamHelper = StreamHelper(authData)
.using(HttpClient.getPreferredClient())
.using(HttpClient.getPreferredClient(application))
val liveData: MutableLiveData<ViewState> = MutableLiveData()
var streamBundle: StreamBundle = StreamBundle()

View File

@@ -41,7 +41,7 @@ class ReviewViewModel(application: Application) : BaseAndroidViewModel(applicati
.getAuthData()
var reviewsHelper: ReviewsHelper = ReviewsHelper(authData)
.using(HttpClient.getPreferredClient())
.using(HttpClient.getPreferredClient(application))
val liveData: MutableLiveData<ReviewCluster> = MutableLiveData()

View File

@@ -38,7 +38,7 @@ class AppSalesViewModel(application: Application) : BaseAndroidViewModel(applica
private val authData: AuthData = AuthProvider.with(application).getAuthData()
private val appSalesHelper: AppSalesHelper =
AppSalesHelper(authData).using(HttpClient.getPreferredClient())
AppSalesHelper(authData).using(HttpClient.getPreferredClient(application))
private var page: Int = 0
private val appList: MutableList<App> = mutableListOf()

View File

@@ -45,7 +45,7 @@ class SearchResultViewModel(application: Application) : BaseAndroidViewModel(app
private val webSearchHelper: WebSearchHelper = WebSearchHelper(authData)
private val searchHelper: SearchHelper = SearchHelper(authData)
.using(HttpClient.getPreferredClient())
.using(HttpClient.getPreferredClient(application))
val liveData: MutableLiveData<SearchBundle> = MutableLiveData()

View File

@@ -40,7 +40,7 @@ class SearchSuggestionViewModel(application: Application) : AndroidViewModel(app
private val webSearchHelper: WebSearchHelper = WebSearchHelper(authData)
private val searchHelper: SearchHelper = SearchHelper(authData)
.using(HttpClient.getPreferredClient())
.using(HttpClient.getPreferredClient(application))
val liveSearchSuggestions: MutableLiveData<List<SearchSuggestEntry>> = MutableLiveData()

View File

@@ -40,7 +40,7 @@ class SubCategoryClusterViewModel(application: Application) : BaseAndroidViewMod
var authData: AuthData = AuthProvider.with(application).getAuthData()
var categoryHelper: CategoryHelper = CategoryHelper(authData)
.using(HttpClient.getPreferredClient())
.using(HttpClient.getPreferredClient(application))
val liveData: MutableLiveData<ViewState> = MutableLiveData()
var streamBundle: StreamBundle = StreamBundle()

View File

@@ -37,7 +37,7 @@ abstract class BaseChartViewModel(application: Application) : BaseAndroidViewMod
private val authData: AuthData = AuthProvider.with(application).getAuthData()
private val topChartsHelper: TopChartsHelper =
TopChartsHelper(authData).using(HttpClient.getPreferredClient())
TopChartsHelper(authData).using(HttpClient.getPreferredClient(application))
lateinit var type: TopChartsHelper.Type
lateinit var chart: TopChartsHelper.Chart