Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into HEAD
Browse files Browse the repository at this point in the history
  • Loading branch information
Ginder-Singh committed Feb 12, 2025
2 parents f842aa8 + 114f67f commit 574c054
Show file tree
Hide file tree
Showing 148 changed files with 6,566 additions and 1,302 deletions.
6 changes: 2 additions & 4 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,10 @@ UploadToNexusPublic:
- apk add jq
- authToken=$(curl -X POST https://pki.int.windscribe.com:8200/v1/auth/approle/login -d "role_id=$role_id&secret_id=$secret_id" | jq --raw-output '.auth.client_token')
- secretData=$(curl -H "X-Vault-Token:$authToken" -X GET https://pki.int.windscribe.com:8200/v1/kv/cicd/client-android)
- echo $secretData
- signProperties=$(echo "$secretData" | jq '.data."sign.properties"')
- echo $signProperties
- echo "$signProperties" | base64 -d -i > $SIGN_PROPERTIES_PATH
- buildProperties=$(echo "$secretData" | jq '.data."build.properties"')
- echo "$buildProperties" | base64 -d -i > $BUILD_PROPERTIES_PATH
- uploadKey=$(echo "$secretData" | jq '.data.android_play_store_upload_key')
- echo "$uploadKey" | base64 -d -i > $GCM_KEY_PATH
- signKey=$(echo "$secretData" | jq '.data.android_play_store_sign_key')
Expand All @@ -106,7 +106,6 @@ UploadToNexusPublic:
- export VERSION_NAME=${MAJOR}.${MINOR}.${VERSION_CODE}
- touch ${CHANGE_LOG_PATH}${VERSION_CODE}.txt
- export ANDROID_NDK=$(ls -d $ANDROID_SDK_ROOT/ndk/*/ | head -n 1)
- tools/build_wstunnel.sh

# update build version to same as play store.
".signKeepBuildNumber":
Expand Down Expand Up @@ -139,7 +138,6 @@ UploadToNexusPublic:
- export VERSION_NAME=${MAJOR}.${MINOR}.${VERSION_CODE}
- touch ${CHANGE_LOG_PATH}${VERSION_CODE}.txt
- export ANDROID_NDK=$(ls -d $ANDROID_SDK_ROOT/ndk/*/ | head -n 1)
- tools/build_wstunnel.sh


Slack:
Expand Down
46 changes: 46 additions & 0 deletions .gitlab/issue_templates/release.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Release Process for Android App

This document outlines the steps to follow when releasing the Android app, including creating a release branch, internal and Open Beta distribution, QA testing, and final Play store submission.

- [ ] Feature Freeze Discussion
- **Timing:** Prior to the release cycle, schedule a feature freeze discussion with the team.
- **Objective:** Review all pending features and ensure only necessary changes are pushed to the release branch. Bug fixes are still allowed
post-freeze.

- [ ] Create Release Branch
- **Branching:** Create a release branch from the main branch.
```bash
git checkout -b release-branch-x.x.x
```
- [ ] Test all issues locally as much as possible.
- [ ] Send release to internal Qa on Testflight and also change version info in gitlab ENV variables.
- [ ] Fix Qa feedback, update release and repeat until full Qa pass is received.
- [ ] Prepare Open beta changelog and update.
- [ ] Release app to Open beta.
- [ ] Wait for regressions , major issues and provide hotfixes.
- [ ] Fix Users feedback, update release and repeat.
- [ ] Prepare final changelog and update in fastlane/metadata/android/en-US/changelogs
- [ ] Send app for play store review.
- [ ] Start stagged rollout with 2% Users and keep increasing it every other day.
- [ ] Merge release branch in to main
- [ ] Create tag from main branch
```bash
git tag -a vX.X.X -m "Release X.X.X"
git push origin vX.X.X
```
- [ ] Sync code to github and create a tagged release.
- [ ] Create admin panel release for website(android, AndroidTV, FireTV) and verify changes on https://www-staging.windscribe.com/
- [ ] Create admin panel release for android-direct apk and android-direct-tv-apk.
- [ ] Create Fdroid MR and follow up on release to Fdroid.
- [ ] Create Amazon app store release.
- [ ] Create Huawei store release.
- [ ] For hotfix Create new branch from tag, make fix, test changes, merge in to main branch and create tag for hotfix.
```bash
git checkout -b hotfix-IssueId-X.X.X tags/vX.X.X
```
### Note: use following naming convention for branches and tags.
1. For Tags vMajor.Minor.BuildNumber
2. For Release branch release-branch-Major.Minor.BuildNumber
3. For Hotfix branch hotfix-IssueId-Major.Minor.BuildNumber
4. For Feature branch feature-IssueId-IssueTitle
5. Always use mobile Build number which is odd number.(113, 213 etc.)
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ class FirebaseManagerImpl(private val context: Windscribe) : FirebaseManager {

}

override fun getFirebaseToken(callback: (MutableMap<String, String>) -> Unit) {
callback(mutableMapOf())
override fun getFirebaseToken(callback: (String?) -> Unit) {
callback(null)
}

override val isPlayStoreInstalled: Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,23 @@ import com.google.firebase.FirebaseOptions
import com.google.firebase.messaging.FirebaseMessaging
import com.windscribe.vpn.BuildConfig
import com.windscribe.vpn.Windscribe
import com.windscribe.vpn.constants.NetworkKeyConstants
import com.windscribe.vpn.services.FirebaseManager
import org.slf4j.LoggerFactory

class FireBaseManagerImpl(private val context: Windscribe): FirebaseManager {
private val logger = LoggerFactory.getLogger("firebase_m")
override fun getFirebaseToken(callback: (MutableMap<String, String>) -> Unit) {
val sessionMap: MutableMap<String, String> = java.util.HashMap()
override fun getFirebaseToken(callback: (String?) -> Unit) {
var token: String? = null
if (BuildConfig.API_KEY.isEmpty()) {
callback(sessionMap)
callback(token)
} else {
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
if (!task.isSuccessful) {
logger.debug("Failed to get token.")
} else {
val newToken = task.result
sessionMap[NetworkKeyConstants.FIREBASE_DEVICE_ID_KEY] = newToken
token = task.result
}
callback(sessionMap)
callback(token)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class WindscribeReviewManagerImpl(
}

private fun daysSinceLogin(): Long {
val milliSeconds1 = preferencesHelper.loginTime.time
val milliSeconds1 = preferencesHelper.loginTime?.time ?: Date().time
val milliSeconds2 = Date().time
val periodSeconds = (milliSeconds2 - milliSeconds1) / 1000
return periodSeconds / 60 / 60 / 24
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ class ActivityInteractorImpl(
override fun getDebugFilePath(): String {
return if (advanceParameterRepository.showStrongSwanLog()) {
"${appContext.filesDir}/charon.log"
} else if(advanceParameterRepository.showWgLog()) {
"${appContext.filesDir}/wireguard_log.txt"
} else {
appContext.cacheDir.path + PreferencesKeyConstants.DEBUG_LOG_FILE_NAME
}
Expand Down Expand Up @@ -388,7 +390,7 @@ class ActivityInteractorImpl(
}

private fun elapsedTwoDayAfterLogin(): Boolean {
val milliSeconds1 = preferenceHelper.loginTime.time
val milliSeconds1 = preferenceHelper.loginTime?.time ?: Date().time
val milliSeconds2 = Date().time
val periodSeconds = (milliSeconds2 - milliSeconds1) / 1000
val elapsedDays = periodSeconds / 60 / 60 / 24
Expand Down
31 changes: 29 additions & 2 deletions base/src/main/java/com/windscribe/vpn/alert/ForegroundAlert.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ fun showRetryDialog(message: String, retryCallBack: () -> Unit, cancelCallBack:
dialog.dismiss()
if (which == AlertDialog.BUTTON_POSITIVE) {
retryCallBack()
}else if(which == AlertDialog.BUTTON_NEGATIVE){
} else if (which == AlertDialog.BUTTON_NEGATIVE) {
cancelCallBack()
}
}
Expand Down Expand Up @@ -110,12 +110,39 @@ fun showAlertDialog(
positionButtonLabel,
DialogInterface.OnClickListener(function = listener)
)
setNegativeButton(negativeButtonLabel, DialogInterface.OnClickListener(function = listener))
setNegativeButton(
negativeButtonLabel,
DialogInterface.OnClickListener(function = listener)
)
show()
}
}
}

fun showErrorDialog(activity: Activity, message: String, callBack: () -> Unit) {
val builder = createDialogBuilder(activity, message)
activity.let {
it.runOnUiThread {
builder.setOnDismissListener {
callBack()
}
builder.setOnCancelListener {
callBack()
}
val listener = { dialog: DialogInterface, _: Int ->
dialog.dismiss()
}
with(builder) {
setNeutralButton(
appContext.getString(R.string.ok),
DialogInterface.OnClickListener(function = listener)
)
show()
}
}
}
}

fun safeDialog(block: (activity: Activity) -> Unit) {
appContext.activeActivity?.let {
it.runOnUiThread {
Expand Down
33 changes: 22 additions & 11 deletions base/src/main/java/com/windscribe/vpn/api/ApiCallManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.windscribe.vpn.api.response.AddEmailResponse
import com.windscribe.vpn.api.response.ApiErrorResponse
import com.windscribe.vpn.api.response.BillingPlanResponse
import com.windscribe.vpn.api.response.ClaimAccountResponse
import com.windscribe.vpn.api.response.ClaimVoucherCodeResponse
import com.windscribe.vpn.api.response.GenericResponseClass
import com.windscribe.vpn.api.response.GenericSuccess
import com.windscribe.vpn.api.response.GetMyIpResponse
Expand Down Expand Up @@ -75,10 +76,10 @@ open class ApiCallManager @Inject constructor(private val apiFactory: ProtectedA
}
}

override fun claimAccount(username: String, password: String, email: String): Single<GenericResponseClass<ClaimAccountResponse?, ApiErrorResponse?>> {
override fun claimAccount(username: String, password: String, email: String, voucherCode: String?): Single<GenericResponseClass<ClaimAccountResponse?, ApiErrorResponse?>> {
return Single.create { sub ->
if (checkSession(sub)) return@create
val callback = wsNetServerAPI.claimAccount(preferencesHelper.sessionHash, username, password, email, "1") { code, json ->
val callback = wsNetServerAPI.claimAccount(preferencesHelper.sessionHash, username, password, email, voucherCode ?: "", "1") { code, json ->
buildResponse(sub, code, json, ClaimAccountResponse::class.java)
}
sub.setCancellable { callback.cancel() }
Expand Down Expand Up @@ -155,10 +156,10 @@ open class ApiCallManager @Inject constructor(private val apiFactory: ProtectedA
}
}

override fun getSessionGeneric(extraParams: Map<String, String>?): Single<GenericResponseClass<UserSessionResponse?, ApiErrorResponse?>> {
override fun getSessionGeneric(firebaseToken: String?): Single<GenericResponseClass<UserSessionResponse?, ApiErrorResponse?>> {
return Single.create { sub ->
if (checkSession(sub)) return@create
val callback = wsNetServerAPI.session(preferencesHelper.sessionHash) { code, json ->
val callback = wsNetServerAPI.session(preferencesHelper.sessionHash, "", firebaseToken ?: "") { code, json ->
buildResponse(sub, code, json, UserSessionResponse::class.java)
}
sub.setCancellable { callback.cancel() }
Expand Down Expand Up @@ -203,16 +204,26 @@ open class ApiCallManager @Inject constructor(private val apiFactory: ProtectedA
}
}

override fun signUserIn(username: String, password: String, referringUsername: String?, email: String?): Single<GenericResponseClass<UserRegistrationResponse?, ApiErrorResponse?>> {
override fun signUserIn(username: String, password: String, referringUsername: String?, email: String?, voucherCode: String?): Single<GenericResponseClass<UserRegistrationResponse?, ApiErrorResponse?>> {
return Single.create { sub ->
val callback = wsNetServerAPI.signup(username, password, referringUsername ?: "", email
?: "") { code, json ->
?: "", voucherCode ?: "") { code, json ->
buildResponse(sub, code, json, UserRegistrationResponse::class.java)
}
sub.setCancellable { callback.cancel() }
}
}

override fun claimVoucherCode(voucherCode: String): Single<GenericResponseClass<ClaimVoucherCodeResponse?, ApiErrorResponse?>> {
return Single.create { sub ->
if (checkSession(sub)) return@create
val callback = wsNetServerAPI.claimVoucherCode(preferencesHelper.sessionHash, voucherCode) { code, json ->
buildResponse(sub, code, json, ClaimVoucherCodeResponse::class.java)
}
sub.setCancellable { callback.cancel() }
}
}

override fun signUpUsingToken(token: String): Single<GenericResponseClass<UserRegistrationResponse?, ApiErrorResponse?>> {
return Single.create { sub ->
val callback = wsNetServerAPI.signupUsingToken(token) { code, json ->
Expand Down Expand Up @@ -372,10 +383,10 @@ open class ApiCallManager @Inject constructor(private val apiFactory: ProtectedA

private fun <T> buildResponse(sub: SingleEmitter<GenericResponseClass<T?, ApiErrorResponse?>>, code: Int, responseDataString: String, modelType: Class<T>) {
when (code) {
1 -> sub.onError(WindScribeException("Unknown network error."))
2 -> sub.onError(WindScribeException("No network available."))
3 -> sub.onError(WindScribeException("Server returned incorrect response."))
4 -> sub.onError(WindScribeException("Unable to reach server."))
1 -> sub.onError(WindScribeException("WSNet: Network failed to connect to server."))
2 -> sub.onError(WindScribeException("WSNet: No network available to reach API."))
3 -> sub.onError(WindScribeException("WSNet: Server returned incorrect json response. Unable to parse it. Response: $responseDataString"))
4 -> sub.onError(WindScribeException("WSNet: All fallback domains have failed."))
else -> {
try {
if (modelType.simpleName.equals("String")) {
Expand All @@ -389,7 +400,7 @@ open class ApiCallManager @Inject constructor(private val apiFactory: ProtectedA
val errorObject = JsonResponseConverter.getErrorClass(JSONObject(responseDataString))
sub.onSuccess(GenericResponseClass(null, errorObject))
} catch (e: Exception) {
sub.onError(WindScribeException("Server returned incorrect response."))
sub.onError(WindScribeException("App: Unable to parse [ $responseDataString ] to ${modelType.simpleName}. ) "))
}
}
}
Expand Down
7 changes: 4 additions & 3 deletions base/src/main/java/com/windscribe/vpn/api/IApiCallManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface IApiCallManager {

fun addUserEmailAddress(email: String): Single<GenericResponseClass<AddEmailResponse?, ApiErrorResponse?>>
fun checkConnectivityAndIpAddress(): Single<GenericResponseClass<GetMyIpResponse?, ApiErrorResponse?>>
fun claimAccount(username: String, password: String, email: String): Single<GenericResponseClass<ClaimAccountResponse?, ApiErrorResponse?>>
fun claimAccount(username: String, password: String, email: String, voucherCode: String?): Single<GenericResponseClass<ClaimAccountResponse?, ApiErrorResponse?>>
fun getBillingPlans(promo: String?): Single<GenericResponseClass<BillingPlanResponse?, ApiErrorResponse?>>
fun getNotifications(pcpID: String?): Single<GenericResponseClass<NewsFeedNotification?, ApiErrorResponse?>>
fun getPortMap(): Single<GenericResponseClass<PortMapResponse?, ApiErrorResponse?>>
Expand All @@ -19,14 +19,15 @@ interface IApiCallManager {
fun getServerCredentials(extraParams: Map<String, String>? = null): Single<GenericResponseClass<ServerCredentialsResponse?, ApiErrorResponse?>>
fun getServerCredentialsForIKev2(extraParams: Map<String, String>? = null): Single<GenericResponseClass<ServerCredentialsResponse?, ApiErrorResponse?>>
fun getServerList(isPro: Boolean, locHash: String, alcList: Array<String>, overriddenCountryCode: String?): Single<GenericResponseClass<String?, ApiErrorResponse?>>
fun getSessionGeneric(extraParams: Map<String, String>? = null): Single<GenericResponseClass<UserSessionResponse?, ApiErrorResponse?>>
fun getSessionGeneric(firebaseToken: String?): Single<GenericResponseClass<UserSessionResponse?, ApiErrorResponse?>>
fun getStaticIpList(deviceID: String?): Single<GenericResponseClass<StaticIPResponse?, ApiErrorResponse?>>
fun logUserIn(username: String, password: String, twoFa: String?): Single<GenericResponseClass<UserLoginResponse?, ApiErrorResponse?>>
fun getWebSession(): Single<GenericResponseClass<WebSession?, ApiErrorResponse?>>
fun recordAppInstall(): Single<GenericResponseClass<String?, ApiErrorResponse?>>
fun resendUserEmailAddress(extraParams: Map<String, String>? = null): Single<GenericResponseClass<AddEmailResponse?, ApiErrorResponse?>>
fun sendTicket(supportEmail: String, supportName: String, supportSubject: String, supportMessage: String, supportCategory: String, type: String, channel: String): Single<GenericResponseClass<TicketResponse?, ApiErrorResponse?>>
fun signUserIn(username: String, password: String, referringUsername: String?, email: String?): Single<GenericResponseClass<UserRegistrationResponse?, ApiErrorResponse?>>
fun signUserIn(username: String, password: String, referringUsername: String?, email: String?, voucherCode: String?): Single<GenericResponseClass<UserRegistrationResponse?, ApiErrorResponse?>>
fun claimVoucherCode(voucherCode: String): Single<GenericResponseClass<ClaimVoucherCodeResponse?, ApiErrorResponse?>>
fun signUpUsingToken(token: String): Single<GenericResponseClass<UserRegistrationResponse?, ApiErrorResponse?>>
fun verifyPurchaseReceipt(purchaseToken: String, gpPackageName: String, gpProductId: String, type: String, amazonUserId: String): Single<GenericResponseClass<GenericSuccess?, ApiErrorResponse?>>
fun verifyExpressLoginCode(loginCode: String): Single<GenericResponseClass<VerifyExpressLoginResponse?, ApiErrorResponse?>>
Expand Down
38 changes: 21 additions & 17 deletions base/src/main/java/com/windscribe/vpn/api/ProtectedApiFactory.kt
Original file line number Diff line number Diff line change
@@ -1,31 +1,35 @@
package com.windscribe.vpn.api

import com.windscribe.vpn.constants.NetworkKeyConstants
import okhttp3.ConnectionPool
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import java.net.Proxy
import javax.inject.Inject
import java.util.concurrent.TimeUnit.MINUTES
import java.util.concurrent.TimeUnit.SECONDS

class ProtectedApiFactory @Inject constructor(
private var retrofitBuilder: Retrofit.Builder,
okHttpClientBuilder: OkHttpClient.Builder
private val retrofitBuilder: Retrofit.Builder,
okHttpClient: OkHttpClient.Builder
) {
private var protectedHttpClient: OkHttpClient? = null

init {
protectedHttpClient =
okHttpClientBuilder.proxy(Proxy.NO_PROXY).socketFactory(VPNBypassSocketFactory())
.build()
private val mRetrofit: Retrofit
fun createApi(url: String): ApiService {
return mRetrofit.newBuilder().baseUrl(url)
.build().create(ApiService::class.java)
}

fun createApi(url: String): ApiService {
protectedHttpClient?.connectionPool?.evictAll()
protectedHttpClient?.socketFactory?.createSocket()
return retrofitBuilder
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(protectedHttpClient!!).baseUrl(url)
.build().create(ApiService::class.java)
init {
okHttpClient.connectTimeout(NetworkKeyConstants.NETWORK_REQUEST_CONNECTION_TIMEOUT, SECONDS)
okHttpClient.readTimeout(5, SECONDS)
okHttpClient.writeTimeout(5, SECONDS)
val connectionPool = ConnectionPool(0, 5, MINUTES)
okHttpClient.connectionPool(connectionPool)
mRetrofit = retrofitBuilder.baseUrl("https://api.windscribe.com")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient.build())
.build()
}
}
Loading

0 comments on commit 574c054

Please sign in to comment.