From 4f40e336072a74702d616ed00fbc09b44b98c28a Mon Sep 17 00:00:00 2001 From: Zorica Stojchevska Date: Wed, 8 Sep 2021 18:25:59 +0200 Subject: [PATCH 01/11] Revert "Fixes test for class FetchContactsData, they were returning `getSeconds` method not found" This reverts commit 6dd67637 --- .../ch/protonmail/android/usecase/fetch/FetchContactsData.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/ch/protonmail/android/usecase/fetch/FetchContactsData.kt b/app/src/main/java/ch/protonmail/android/usecase/fetch/FetchContactsData.kt index 58141a1bb..4cc860946 100644 --- a/app/src/main/java/ch/protonmail/android/usecase/fetch/FetchContactsData.kt +++ b/app/src/main/java/ch/protonmail/android/usecase/fetch/FetchContactsData.kt @@ -26,8 +26,8 @@ import ch.protonmail.android.utils.extensions.filter import ch.protonmail.android.worker.FetchContactsDataWorker import ch.protonmail.android.worker.FetchContactsEmailsWorker import timber.log.Timber -import java.util.concurrent.TimeUnit import javax.inject.Inject +import kotlin.time.seconds class FetchContactsData @Inject constructor( private val fetchContactsDataWorker: FetchContactsDataWorker.Enqueuer, @@ -35,7 +35,7 @@ class FetchContactsData @Inject constructor( ) { operator fun invoke(): LiveData { - fetchContactsEmailsWorker.enqueue(TimeUnit.SECONDS.toMillis(2)) + fetchContactsEmailsWorker.enqueue(2.seconds.toLongMilliseconds()) .filter { it?.state?.isFinished == true } .map { workInfo -> Timber.v("Finished contacts emails worker State ${workInfo.state}") From eb65ca701a4f8f5e7a5273d436bcc848a55ff6cb Mon Sep 17 00:00:00 2001 From: Zorica Stojchevska Date: Wed, 8 Sep 2021 18:27:22 +0200 Subject: [PATCH 02/11] Revert "The "server not reachable" banner was blinking and showing up multiple times. Because when some specific exceptions were received from the server we were assuming that server is not reachable, right away." This reverts commit 8e865437 --- .../messageDetails/MessageDetailsActivity.kt | 7 +- .../interceptors/BaseRequestInterceptor.kt | 2 +- .../ProtonMailRequestInterceptor.kt | 6 +- .../api/segments/event/FetchUpdatesJob.kt | 85 ++++++++++++------- .../api/services/ConnectivityService.kt | 3 + .../ch/protonmail/android/core/Constants.kt | 1 - .../android/core/QueueNetworkUtil.kt | 14 ++- .../android/jobs/ProtonMailEndlessJob.java | 2 +- .../android/usecase/VerifyConnection.kt | 3 +- .../utils/crypto/ServerTimeInterceptor.kt | 2 +- .../viewmodel/ConnectivityBaseViewModel.kt | 2 +- .../protonmail/android/worker/PingWorker.kt | 1 + 12 files changed, 75 insertions(+), 53 deletions(-) diff --git a/app/src/main/java/ch/protonmail/android/activities/messageDetails/MessageDetailsActivity.kt b/app/src/main/java/ch/protonmail/android/activities/messageDetails/MessageDetailsActivity.kt index b4c809b31..b92fef154 100644 --- a/app/src/main/java/ch/protonmail/android/activities/messageDetails/MessageDetailsActivity.kt +++ b/app/src/main/java/ch/protonmail/android/activities/messageDetails/MessageDetailsActivity.kt @@ -461,10 +461,11 @@ internal class MessageDetailsActivity : this, { isConnectionActive -> Timber.v("isConnectionActive:${isConnectionActive.name}") - if (isConnectionActive != Constants.ConnectionState.CONNECTED) { - showNoConnSnackExtended(isConnectionActive) - } else { + if (isConnectionActive == Constants.ConnectionState.CONNECTED) { hideNoConnSnackExtended() + viewModel.fetchMessageDetails(false) + } else { + showNoConnSnackExtended(isConnectionActive) } } ) diff --git a/app/src/main/java/ch/protonmail/android/api/interceptors/BaseRequestInterceptor.kt b/app/src/main/java/ch/protonmail/android/api/interceptors/BaseRequestInterceptor.kt index 430d1438f..cdf2f314f 100644 --- a/app/src/main/java/ch/protonmail/android/api/interceptors/BaseRequestInterceptor.kt +++ b/app/src/main/java/ch/protonmail/android/api/interceptors/BaseRequestInterceptor.kt @@ -150,7 +150,7 @@ abstract class BaseRequestInterceptor( } response.code() == RESPONSE_CODE_SERVICE_UNAVAILABLE -> { // 503 Timber.d("'service unavailable' when processing request") - networkUtils.retryPingAsPreviousRequestWasInconclusive() + networkUtils.setCurrentlyDoesntHaveConnectivity() } response.code() == RESPONSE_CODE_UNPROCESSABLE_ENTITY -> { Timber.d("'unprocessable entity' when processing request") diff --git a/app/src/main/java/ch/protonmail/android/api/interceptors/ProtonMailRequestInterceptor.kt b/app/src/main/java/ch/protonmail/android/api/interceptors/ProtonMailRequestInterceptor.kt index f2d066d65..568aabd7b 100644 --- a/app/src/main/java/ch/protonmail/android/api/interceptors/ProtonMailRequestInterceptor.kt +++ b/app/src/main/java/ch/protonmail/android/api/interceptors/ProtonMailRequestInterceptor.kt @@ -50,12 +50,12 @@ class ProtonMailRequestInterceptor private constructor( var response: Response? = null try { requestCount++ - Timber.d("Intercept: advancing request with url: " + request.url()) + Timber.d("Intercept: advancing request with url: " + request.url) response = chain.proceed(request) } catch (exception: IOException) { - Timber.d(exception, "Intercept: IOException with url: " + request.url()) - networkUtils.retryPingAsPreviousRequestWasInconclusive() + Timber.d(exception, "Intercept: IOException with url: " + request.url) + networkUtils.setConnectivityHasFailed(exception) } requestCount-- diff --git a/app/src/main/java/ch/protonmail/android/api/segments/event/FetchUpdatesJob.kt b/app/src/main/java/ch/protonmail/android/api/segments/event/FetchUpdatesJob.kt index 53e621f30..52502a7a4 100644 --- a/app/src/main/java/ch/protonmail/android/api/segments/event/FetchUpdatesJob.kt +++ b/app/src/main/java/ch/protonmail/android/api/segments/event/FetchUpdatesJob.kt @@ -16,49 +16,68 @@ * You should have received a copy of the GNU General Public License * along with ProtonMail. If not, see https://www.gnu.org/licenses/. */ -package ch.protonmail.android.api.segments.event +package ch.protonmail.android.api.segments.event; -import ch.protonmail.android.api.AccountManager -import ch.protonmail.android.api.models.room.messages.MessagesDatabaseFactory -import ch.protonmail.android.core.ProtonMailApplication -import ch.protonmail.android.events.FetchUpdatesEvent -import ch.protonmail.android.events.Status -import ch.protonmail.android.jobs.Priority -import ch.protonmail.android.jobs.ProtonMailBaseJob -import ch.protonmail.android.utils.AppUtil -import com.birbit.android.jobqueue.Params -import timber.log.Timber -import java.net.ConnectException -import java.util.concurrent.TimeUnit +import androidx.annotation.Nullable; -class FetchUpdatesJob internal constructor(private val eventManager: EventManager) : ProtonMailBaseJob( - Params(Priority.HIGH).requireNetwork() -) { +import com.birbit.android.jobqueue.Params; - constructor() : this(ProtonMailApplication.getApplication().eventManager) +import java.net.ConnectException; +import java.util.List; +import java.util.concurrent.TimeUnit; - @Throws(Throwable::class) - override fun onRun() { - val messageDao = MessagesDatabaseFactory.getInstance(applicationContext).getDatabase() +import ch.protonmail.android.api.AccountManager; +import ch.protonmail.android.api.models.room.messages.MessagesDatabase; +import ch.protonmail.android.api.models.room.messages.MessagesDatabaseFactory; +import ch.protonmail.android.core.ProtonMailApplication; +import ch.protonmail.android.events.ConnectivityEvent; +import ch.protonmail.android.events.FetchUpdatesEvent; +import ch.protonmail.android.events.Status; +import ch.protonmail.android.jobs.Priority; +import ch.protonmail.android.jobs.ProtonMailBaseJob; +import ch.protonmail.android.utils.AppUtil; +import ch.protonmail.android.utils.Logger; + +public class FetchUpdatesJob extends ProtonMailBaseJob { + + private static final String TAG_FETCH_UPDATES_JOB = "FetchUpdatesJob"; + private EventManager eventManager; + + FetchUpdatesJob(EventManager eventManager) { + super(new Params(Priority.HIGH).requireNetwork()); + this.eventManager = eventManager; + } + + public FetchUpdatesJob() { + this(ProtonMailApplication.getApplication().getEventManager()); + } + + @Override + public void onRun() throws Throwable { + MessagesDatabase messagesDatabase = MessagesDatabaseFactory.Companion.getInstance(getApplicationContext()).getDatabase(); if (!getQueueNetworkUtil().isConnected()) { - Timber.i("no network cannot fetch updates") - return + Logger.doLog(TAG_FETCH_UPDATES_JOB, "no network cannot fetch updates"); + AppUtil.postEventOnUi(new FetchUpdatesEvent(Status.NO_NETWORK)); + return; } - // check for expired messages in the cache and delete them - val currentTime = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) - messageDao.deleteExpiredMessages(currentTime) + //check for expired messages in the cache and delete them + long currentTime = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()); + messagesDatabase.deleteExpiredMessages(currentTime); try { - val loggedInUsers = AccountManager.getInstance(ProtonMailApplication.getApplication()).getLoggedInUsers() - eventManager.start(loggedInUsers) - AppUtil.postEventOnUi(FetchUpdatesEvent(Status.SUCCESS)) - } catch (e: Exception) { - Timber.e(e, "FetchUpdatesJob has failed") - if (e is ConnectException) { - getQueueNetworkUtil().retryPingAsPreviousRequestWasInconclusive() + List loggedInUsers = AccountManager.Companion.getInstance(ProtonMailApplication.getApplication()).getLoggedInUsers(); + eventManager.start(loggedInUsers); + AppUtil.postEventOnUi(new FetchUpdatesEvent(Status.SUCCESS)); + } catch (Exception e) { + if (e.getCause() instanceof ConnectException) { + AppUtil.postEventOnUi(new ConnectivityEvent(false)); } + AppUtil.postEventOnUi(new FetchUpdatesEvent(Status.FAILED)); } } - override fun onProtonCancel(cancelReason: Int, throwable: Throwable?) {} + @Override + protected void onProtonCancel(int cancelReason, @Nullable Throwable throwable) { + AppUtil.postEventOnUi(new FetchUpdatesEvent(Status.FAILED)); + } } diff --git a/app/src/main/java/ch/protonmail/android/api/services/ConnectivityService.kt b/app/src/main/java/ch/protonmail/android/api/services/ConnectivityService.kt index a0c94bd4a..12c24d9fe 100644 --- a/app/src/main/java/ch/protonmail/android/api/services/ConnectivityService.kt +++ b/app/src/main/java/ch/protonmail/android/api/services/ConnectivityService.kt @@ -27,6 +27,7 @@ import android.net.ConnectivityManager import android.os.Build import ch.protonmail.android.api.segments.event.AlarmReceiver import ch.protonmail.android.core.ProtonMailApplication +import ch.protonmail.android.events.ConnectivityEvent import ch.protonmail.android.receivers.ConnectivityBroadcastReceiver import ch.protonmail.android.utils.AppUtil import android.content.Intent @@ -62,5 +63,7 @@ class ConnectivityService : JobService(), ConnectivityBroadcastReceiver.Connecti alarmReceiver.setAlarm(this) ProtonMailApplication.getApplication().startJobManager() } + + AppUtil.postEventOnUi(ConnectivityEvent(isOnline)) } } diff --git a/app/src/main/java/ch/protonmail/android/core/Constants.kt b/app/src/main/java/ch/protonmail/android/core/Constants.kt index 6809eadc9..f20da468e 100644 --- a/app/src/main/java/ch/protonmail/android/core/Constants.kt +++ b/app/src/main/java/ch/protonmail/android/core/Constants.kt @@ -391,7 +391,6 @@ object Constants { } enum class ConnectionState { - PING_NEEDED, CONNECTED, NO_INTERNET, CANT_REACH_SERVER; diff --git a/app/src/main/java/ch/protonmail/android/core/QueueNetworkUtil.kt b/app/src/main/java/ch/protonmail/android/core/QueueNetworkUtil.kt index 59dac0075..ae1ce685c 100644 --- a/app/src/main/java/ch/protonmail/android/core/QueueNetworkUtil.kt +++ b/app/src/main/java/ch/protonmail/android/core/QueueNetworkUtil.kt @@ -78,10 +78,7 @@ class QueueNetworkUtil @Inject constructor( } @Synchronized - fun updateRealConnectivity( - serverAccessible: Boolean, - connectionState: Constants.ConnectionState = Constants.ConnectionState.CONNECTED - ) { + private fun updateRealConnectivity(serverAccessible: Boolean) { isServerAccessible = serverAccessible if (serverAccessible) { @@ -95,7 +92,7 @@ class QueueNetworkUtil @Inject constructor( val mayEmit = emissionTimeDelta > DISCONNECTION_EMISSION_WINDOW_MS if (mayEmit) { lastEmissionTime = currentTime - backendExceptionFlow.value = connectionState + backendExceptionFlow.value = Constants.ConnectionState.CANT_REACH_SERVER } } } @@ -103,8 +100,7 @@ class QueueNetworkUtil @Inject constructor( fun isConnected(): Boolean = hasConn(false) fun setCurrentlyHasConnectivity() = updateRealConnectivity(true) - fun retryPingAsPreviousRequestWasInconclusive() = - updateRealConnectivity(false, Constants.ConnectionState.PING_NEEDED) + fun setCurrentlyDoesntHaveConnectivity() = updateRealConnectivity(false) fun setConnectivityHasFailed(throwable: Throwable) { // for valid failure types specified below @@ -113,7 +109,7 @@ class QueueNetworkUtil @Inject constructor( when (throwable) { is SocketTimeoutException, is GeneralSecurityException, // e.g. CertificateException - is SSLException -> updateRealConnectivity(false, Constants.ConnectionState.CANT_REACH_SERVER) + is SSLException -> updateRealConnectivity(false) else -> Timber.d("connectivityHasFailed ignoring exception: $throwable") } } @@ -141,6 +137,8 @@ class QueueNetworkUtil @Inject constructor( } } return hasConnection + + } } diff --git a/app/src/main/java/ch/protonmail/android/jobs/ProtonMailEndlessJob.java b/app/src/main/java/ch/protonmail/android/jobs/ProtonMailEndlessJob.java index fab5c731f..62ea07ab5 100644 --- a/app/src/main/java/ch/protonmail/android/jobs/ProtonMailEndlessJob.java +++ b/app/src/main/java/ch/protonmail/android/jobs/ProtonMailEndlessJob.java @@ -50,7 +50,7 @@ protected RetryConstraint shouldReRunOnThrowable(@NonNull Throwable throwable, i if (throwable instanceof Exception) { if (throwable.getCause() instanceof IOException) { shouldReschedule = true; - getQueueNetworkUtil().retryPingAsPreviousRequestWasInconclusive(); + getQueueNetworkUtil().setConnectivityHasFailed(throwable); } } return RetryConstraint.RETRY; diff --git a/app/src/main/java/ch/protonmail/android/usecase/VerifyConnection.kt b/app/src/main/java/ch/protonmail/android/usecase/VerifyConnection.kt index e7c6e637c..e139e79cf 100644 --- a/app/src/main/java/ch/protonmail/android/usecase/VerifyConnection.kt +++ b/app/src/main/java/ch/protonmail/android/usecase/VerifyConnection.kt @@ -26,6 +26,8 @@ import androidx.work.WorkInfo import ch.protonmail.android.core.Constants import ch.protonmail.android.core.NetworkConnectivityManager import ch.protonmail.android.core.QueueNetworkUtil +import ch.protonmail.android.events.ConnectivityEvent +import ch.protonmail.android.utils.AppUtil import ch.protonmail.android.worker.PingWorker import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter @@ -74,7 +76,6 @@ class VerifyConnection @Inject constructor( connectivityManagerFlow ) .flattenMerge() - .filter { it != Constants.ConnectionState.PING_NEEDED } .onStart { pingWorkerEnqueuer.enqueue() emit( diff --git a/app/src/main/java/ch/protonmail/android/utils/crypto/ServerTimeInterceptor.kt b/app/src/main/java/ch/protonmail/android/utils/crypto/ServerTimeInterceptor.kt index 1ea355859..832412058 100644 --- a/app/src/main/java/ch/protonmail/android/utils/crypto/ServerTimeInterceptor.kt +++ b/app/src/main/java/ch/protonmail/android/utils/crypto/ServerTimeInterceptor.kt @@ -46,7 +46,7 @@ class ServerTimeInterceptor( handleResponse(response) } catch (exception: IOException) { Timber.d(exception, "IOException ${request.url()}") - queueNetworkUtil.retryPingAsPreviousRequestWasInconclusive() + queueNetworkUtil.setConnectivityHasFailed(exception) } if (response == null) { diff --git a/app/src/main/java/ch/protonmail/android/viewmodel/ConnectivityBaseViewModel.kt b/app/src/main/java/ch/protonmail/android/viewmodel/ConnectivityBaseViewModel.kt index 64a26e32a..89d587d3c 100644 --- a/app/src/main/java/ch/protonmail/android/viewmodel/ConnectivityBaseViewModel.kt +++ b/app/src/main/java/ch/protonmail/android/viewmodel/ConnectivityBaseViewModel.kt @@ -52,7 +52,7 @@ open class ConnectivityBaseViewModel @ViewModelInject constructor( verifyConnection() .distinctUntilChanged() .onEach { isConnected -> - if (isConnected == Constants.ConnectionState.CANT_REACH_SERVER) { + if (isConnected != Constants.ConnectionState.CONNECTED) { retryWithDoh() } } diff --git a/app/src/main/java/ch/protonmail/android/worker/PingWorker.kt b/app/src/main/java/ch/protonmail/android/worker/PingWorker.kt index d98f47046..d4f6bced5 100644 --- a/app/src/main/java/ch/protonmail/android/worker/PingWorker.kt +++ b/app/src/main/java/ch/protonmail/android/worker/PingWorker.kt @@ -72,6 +72,7 @@ class PingWorker @WorkerInject constructor( onFailure = { throwable -> Timber.v("Ping isAccessible: failed") queueNetworkUtil.setConnectivityHasFailed(throwable) + AppUtil.postEventOnUi(ConnectivityEvent(false)) failure(throwable) } ) From 34881897adb18f70328c813c2b0cb0618732c40c Mon Sep 17 00:00:00 2001 From: Zorica Stojchevska Date: Wed, 8 Sep 2021 18:28:15 +0200 Subject: [PATCH 03/11] Revert "Rename .java to .kt" This reverts commit b58456e1 --- .../segments/event/{FetchUpdatesJob.kt => FetchUpdatesJob.java} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/src/main/java/ch/protonmail/android/api/segments/event/{FetchUpdatesJob.kt => FetchUpdatesJob.java} (100%) diff --git a/app/src/main/java/ch/protonmail/android/api/segments/event/FetchUpdatesJob.kt b/app/src/main/java/ch/protonmail/android/api/segments/event/FetchUpdatesJob.java similarity index 100% rename from app/src/main/java/ch/protonmail/android/api/segments/event/FetchUpdatesJob.kt rename to app/src/main/java/ch/protonmail/android/api/segments/event/FetchUpdatesJob.java From df215ca8af858c2a0f1a704b38b0470685806399 Mon Sep 17 00:00:00 2001 From: Zorica Stojchevska Date: Wed, 8 Sep 2021 18:28:50 +0200 Subject: [PATCH 04/11] Revert "Moved the showing of the snackbar for `server not reachable` only after the ping returns an error" This reverts commit 6b5b1000 --- .../android/api/interceptors/BaseRequestInterceptor.kt | 1 - .../android/api/interceptors/ProtonMailRequestInterceptor.kt | 5 +++-- .../main/java/ch/protonmail/android/core/QueueNetworkUtil.kt | 1 - .../java/ch/protonmail/android/usecase/VerifyConnection.kt | 2 -- app/src/main/java/ch/protonmail/android/worker/PingWorker.kt | 3 --- .../test/java/ch/protonmail/android/worker/PingWorkerTest.kt | 5 ----- 6 files changed, 3 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/ch/protonmail/android/api/interceptors/BaseRequestInterceptor.kt b/app/src/main/java/ch/protonmail/android/api/interceptors/BaseRequestInterceptor.kt index cdf2f314f..6f1cfa9a2 100644 --- a/app/src/main/java/ch/protonmail/android/api/interceptors/BaseRequestInterceptor.kt +++ b/app/src/main/java/ch/protonmail/android/api/interceptors/BaseRequestInterceptor.kt @@ -150,7 +150,6 @@ abstract class BaseRequestInterceptor( } response.code() == RESPONSE_CODE_SERVICE_UNAVAILABLE -> { // 503 Timber.d("'service unavailable' when processing request") - networkUtils.setCurrentlyDoesntHaveConnectivity() } response.code() == RESPONSE_CODE_UNPROCESSABLE_ENTITY -> { Timber.d("'unprocessable entity' when processing request") diff --git a/app/src/main/java/ch/protonmail/android/api/interceptors/ProtonMailRequestInterceptor.kt b/app/src/main/java/ch/protonmail/android/api/interceptors/ProtonMailRequestInterceptor.kt index 568aabd7b..94bf21abc 100644 --- a/app/src/main/java/ch/protonmail/android/api/interceptors/ProtonMailRequestInterceptor.kt +++ b/app/src/main/java/ch/protonmail/android/api/interceptors/ProtonMailRequestInterceptor.kt @@ -50,11 +50,12 @@ class ProtonMailRequestInterceptor private constructor( var response: Response? = null try { requestCount++ - Timber.d("Intercept: advancing request with url: " + request.url) + Timber.d("Intercept: advancing request with url: " + request.url()) response = chain.proceed(request) } catch (exception: IOException) { - Timber.d(exception, "Intercept: IOException with url: " + request.url) + Timber.d(exception, "Intercept: IOException with url: " + request.url()) + AppUtil.postEventOnUi(ConnectivityEvent(false)) networkUtils.setConnectivityHasFailed(exception) } diff --git a/app/src/main/java/ch/protonmail/android/core/QueueNetworkUtil.kt b/app/src/main/java/ch/protonmail/android/core/QueueNetworkUtil.kt index ae1ce685c..1f9eb32c3 100644 --- a/app/src/main/java/ch/protonmail/android/core/QueueNetworkUtil.kt +++ b/app/src/main/java/ch/protonmail/android/core/QueueNetworkUtil.kt @@ -100,7 +100,6 @@ class QueueNetworkUtil @Inject constructor( fun isConnected(): Boolean = hasConn(false) fun setCurrentlyHasConnectivity() = updateRealConnectivity(true) - fun setCurrentlyDoesntHaveConnectivity() = updateRealConnectivity(false) fun setConnectivityHasFailed(throwable: Throwable) { // for valid failure types specified below diff --git a/app/src/main/java/ch/protonmail/android/usecase/VerifyConnection.kt b/app/src/main/java/ch/protonmail/android/usecase/VerifyConnection.kt index e139e79cf..a6b834080 100644 --- a/app/src/main/java/ch/protonmail/android/usecase/VerifyConnection.kt +++ b/app/src/main/java/ch/protonmail/android/usecase/VerifyConnection.kt @@ -26,8 +26,6 @@ import androidx.work.WorkInfo import ch.protonmail.android.core.Constants import ch.protonmail.android.core.NetworkConnectivityManager import ch.protonmail.android.core.QueueNetworkUtil -import ch.protonmail.android.events.ConnectivityEvent -import ch.protonmail.android.utils.AppUtil import ch.protonmail.android.worker.PingWorker import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter diff --git a/app/src/main/java/ch/protonmail/android/worker/PingWorker.kt b/app/src/main/java/ch/protonmail/android/worker/PingWorker.kt index d4f6bced5..583b38083 100644 --- a/app/src/main/java/ch/protonmail/android/worker/PingWorker.kt +++ b/app/src/main/java/ch/protonmail/android/worker/PingWorker.kt @@ -34,8 +34,6 @@ import androidx.work.WorkerParameters import ch.protonmail.android.api.ProtonMailApiManager import ch.protonmail.android.core.Constants import ch.protonmail.android.core.QueueNetworkUtil -import ch.protonmail.android.events.ConnectivityEvent -import ch.protonmail.android.utils.AppUtil import kotlinx.coroutines.withContext import me.proton.core.util.kotlin.DispatcherProvider import timber.log.Timber @@ -72,7 +70,6 @@ class PingWorker @WorkerInject constructor( onFailure = { throwable -> Timber.v("Ping isAccessible: failed") queueNetworkUtil.setConnectivityHasFailed(throwable) - AppUtil.postEventOnUi(ConnectivityEvent(false)) failure(throwable) } ) diff --git a/app/src/test/java/ch/protonmail/android/worker/PingWorkerTest.kt b/app/src/test/java/ch/protonmail/android/worker/PingWorkerTest.kt index d07d7154f..49bc6cbd2 100644 --- a/app/src/test/java/ch/protonmail/android/worker/PingWorkerTest.kt +++ b/app/src/test/java/ch/protonmail/android/worker/PingWorkerTest.kt @@ -26,15 +26,12 @@ import ch.protonmail.android.api.ProtonMailApiManager import ch.protonmail.android.api.models.ResponseBody import ch.protonmail.android.core.Constants import ch.protonmail.android.core.QueueNetworkUtil -import ch.protonmail.android.utils.AppUtil import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.RelaxedMockK -import io.mockk.justRun import io.mockk.mockk -import io.mockk.mockkStatic import io.mockk.verify import kotlinx.coroutines.test.runBlockingTest import me.proton.core.test.kotlin.TestDispatcherProvider @@ -130,8 +127,6 @@ class PingWorkerTest { // given val ioException = IOException("NetworkError!") val expected = ListenableWorker.Result.failure(WorkerError(ioException.message ?: EMPTY_STRING).toWorkData()) - mockkStatic(AppUtil::class) - justRun { AppUtil.postEventOnUi(any()) } coEvery { api.pingAsync() } throws ioException // when From 3d94ffab92fbad6845e1631cdae7df6e1a3b966b Mon Sep 17 00:00:00 2001 From: Zorica Stojchevska Date: Wed, 8 Sep 2021 18:34:22 +0200 Subject: [PATCH 05/11] Changed build version #comment Just increased the build version name and code for a new release Affected: nothing --- buildSrc/src/main/kotlin/ProtonMail.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/kotlin/ProtonMail.kt b/buildSrc/src/main/kotlin/ProtonMail.kt index 75656f654..aea120676 100644 --- a/buildSrc/src/main/kotlin/ProtonMail.kt +++ b/buildSrc/src/main/kotlin/ProtonMail.kt @@ -22,8 +22,8 @@ * @author Davide Farella */ object ProtonMail { - const val versionName = "1.13.34" - const val versionCode = 780 + const val versionName = "1.13.35" + const val versionCode = 782 const val targetSdk = 30 const val minSdk = 21 From 327c07d93cbd7dd55faf6b78d528296b3de0e435 Mon Sep 17 00:00:00 2001 From: Marino Meneghel Date: Thu, 7 Oct 2021 10:52:10 +0200 Subject: [PATCH 06/11] Avoid logout when changing password after key migration happened When trying to change the mailbox or account password right after a key migration happened, the API request would fail asking the user to logout and login again. This happened because of the `UserKey` param not being set when performing the API request (eg. `key/private`), while after key migration happened that param is always expected to be set (and `Keys` isn't anymore, which will be tackled in the next commit). Since when key migration happens the app receives an event, by "replicating" the logic that exists in `LoginService#setAccountMigrationStatus` also in the eventHandler we can check for the migration and set the `legacyAccount` flag to false if the migration happened. This flag will then be used in the ChangePassword job to define whether params such as `UserKey` should be set to the API request. Please note that compared to the logic currently implemented in the `LoginService`, the one introduced in the events have been corrected to check the keys of all of the user's addresses, since that is the condition that tells us whether the migration did happen. MAILAND-2401 MAILAND-2402 --- .../api/segments/event/EventHandler.kt | 7 + .../api/segments/event/EventManager.kt | 2 - .../api/segments/event/EventHandlerTest.kt | 171 ++++++++++++++++++ 3 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 app/src/test/java/ch/protonmail/android/api/segments/event/EventHandlerTest.kt diff --git a/app/src/main/java/ch/protonmail/android/api/segments/event/EventHandler.kt b/app/src/main/java/ch/protonmail/android/api/segments/event/EventHandler.kt index 511a8ab87..2d37bbc1a 100644 --- a/app/src/main/java/ch/protonmail/android/api/segments/event/EventHandler.kt +++ b/app/src/main/java/ch/protonmail/android/api/segments/event/EventHandler.kt @@ -502,6 +502,13 @@ class EventHandler @AssistedInject constructor( AddressKeyActivationWorker.activateAddressKeysIfNeeded(context, eventAddresses, username) user.setAddresses(addresses) + + val isAccountMigrated = addresses.any { address -> + address.keys.any { key -> + key.signature != null && key.token != null + } + } + user.legacyAccount = !isAccountMigrated } private fun writeContactsUpdates(contactsDatabase: ContactsDatabase, events: List) { diff --git a/app/src/main/java/ch/protonmail/android/api/segments/event/EventManager.kt b/app/src/main/java/ch/protonmail/android/api/segments/event/EventManager.kt index ee869f851..74401a94e 100644 --- a/app/src/main/java/ch/protonmail/android/api/segments/event/EventManager.kt +++ b/app/src/main/java/ch/protonmail/android/api/segments/event/EventManager.kt @@ -31,10 +31,8 @@ import java.io.IOException import javax.inject.Inject import javax.inject.Singleton -// region constants private const val PREF_NEXT_EVENT_ID = "latest_event_id" const val PREF_LATEST_EVENT = "latest_event" -// endregion /** * EventManager manages the fetching of the proper events and delegates their handling. diff --git a/app/src/test/java/ch/protonmail/android/api/segments/event/EventHandlerTest.kt b/app/src/test/java/ch/protonmail/android/api/segments/event/EventHandlerTest.kt new file mode 100644 index 000000000..0b2dd80bc --- /dev/null +++ b/app/src/test/java/ch/protonmail/android/api/segments/event/EventHandlerTest.kt @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2020 Proton Technologies AG + * + * This file is part of ProtonMail. + * + * ProtonMail is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ProtonMail is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ProtonMail. If not, see https://www.gnu.org/licenses/. + */ + +package ch.protonmail.android.api.segments.event + +import ch.protonmail.android.activities.messageDetails.repository.MessageDetailsRepository +import ch.protonmail.android.api.models.EventResponse +import ch.protonmail.android.api.models.Keys +import ch.protonmail.android.api.models.User +import ch.protonmail.android.api.models.address.Address +import ch.protonmail.android.core.UserManager +import ch.protonmail.android.utils.AppUtil +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkStatic +import io.mockk.verify +import java.util.concurrent.CopyOnWriteArrayList +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test + +private const val USERNAME = "username" +private const val UPDATE_EVENT_TYPE = 2 +private const val LEGACY_ADDRESS_EVENT_ID = "legacyAddressEventId" +private const val MIGRATED_ADDRESS_EVENT_ID = "migratedAddressEventId" + +class EventHandlerTest { + + private val mockUser = mockk(relaxUnitFun = true) { + every { notificationSetting } returns 0 + every { addresses } returns CopyOnWriteArrayList() + } + + private val userManager = mockk(relaxUnitFun = true) { + every { getUser(USERNAME) } returns mockUser + } + + private val messageDetailsRepository = mockk(relaxUnitFun = true) + + private val eventHandler = EventHandler( + mockk(), + mockk(), + userManager, + messageDetailsRepository, + mockk(), + mockk(), + mockk(), + mockk(), + mockk(), + mockk(), + mockk(), + USERNAME + ) + + private val addressWithMigratedKey = buildAddress( + MIGRATED_ADDRESS_EVENT_ID, + buildKey( + "Migrated Key Id", + "valid token indicating a successfull migration", + "valid signature indicating a successful migration" + ) + ) + + private val addressWithLegacyKey = buildAddress( + LEGACY_ADDRESS_EVENT_ID, + buildKey("Legacy Key Id", null, null) + ) + + @BeforeTest + fun setUp() { + mockkStatic(AppUtil::class) + every { AppUtil.postEventOnUi(any()) } just Runs + } + + @AfterTest + fun tearDown() { + unmockkStatic(AppUtil::class) + } + + @Test + fun whenReceivingAnAddressesUpdateEventWhereAnyOfTheAddressesKeysHaveNonNullSignatureAndTokenThenMarkTheAccountLocallyAsMigrated() { + val legacyAddressEvent: EventResponse.AddressEventBody = mockk { + every { address } returns addressWithLegacyKey + every { type } returns UPDATE_EVENT_TYPE + every { id } returns LEGACY_ADDRESS_EVENT_ID + } + val migratedAddressEvent: EventResponse.AddressEventBody = mockk { + every { address } returns addressWithMigratedKey + every { type } returns UPDATE_EVENT_TYPE + every { id } returns MIGRATED_ADDRESS_EVENT_ID + } + val response = mockEventResponse(listOf(legacyAddressEvent, migratedAddressEvent)) + + eventHandler.write(response) + + verify { mockUser.legacyAccount = false } + } + + @Test + fun whenReceivingAnAddressesUpdateEventWhereAllOfTheAddressesKeysHaveNullSignatureAndTokenThenMarkTheAccountLocallyAsLegacy() { + val addressEventBody: EventResponse.AddressEventBody = mockk { + every { address } returns addressWithLegacyKey + every { type } returns UPDATE_EVENT_TYPE + every { id } returns LEGACY_ADDRESS_EVENT_ID + } + val response = mockEventResponse(listOf(addressEventBody)) + + eventHandler.write(response) + + verify { mockUser.legacyAccount = true } + } + + private fun mockEventResponse(listOf: List) = + mockk { + every { messageUpdates } returns null + every { contactUpdates } returns null + every { contactEmailsUpdates } returns null + every { mailSettingsUpdates } returns null + every { userSettingsUpdates } returns null + every { labelUpdates } returns null + every { messageCounts } returns null + every { usedSpace } returns 0 + every { userUpdates } returns mockUser + every { addresses } returns listOf + } + + private fun buildKey(keyId: String, signature: String?, token: String?) = Keys( + keyId, + "private key value", + 0, + 0, + signature, + token, + "", + 0 + ) + + private fun buildAddress(addressId: String, key: Keys) = Address( + addressId, + "DomainId", + "email@pm.me", + 0, + 0, + 0, + UPDATE_EVENT_TYPE, + 0, + "namee", + "valid non empty signature", + 0, + listOf(key) + ) +} From 1428d106fa74ee27d0ee94efc99c2d0c1683121f Mon Sep 17 00:00:00 2001 From: Zorica Stojchevska Date: Mon, 11 Oct 2021 11:23:34 +0200 Subject: [PATCH 07/11] Changes logic that generates passphrase for address keys.. When encrypting and decrypting after account has been migrated to use new keys we were previously using one key only (the primary key). After consulting with crypto team this was changed to try all user keys, and generate a passphrase with the first one that succeeds. --- .../android/crypto/AddressCrypto.kt | 61 +++++++++++-------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/ch/protonmail/android/crypto/AddressCrypto.kt b/app/src/main/java/ch/protonmail/android/crypto/AddressCrypto.kt index d82ed9106..3a5e4e929 100644 --- a/app/src/main/java/ch/protonmail/android/crypto/AddressCrypto.kt +++ b/app/src/main/java/ch/protonmail/android/crypto/AddressCrypto.kt @@ -35,6 +35,8 @@ import ch.protonmail.libs.core.utils.encodeToBase64String import com.proton.gopenpgp.armor.Armor import com.proton.gopenpgp.constants.Constants import com.proton.gopenpgp.crypto.KeyRing +import com.proton.gopenpgp.crypto.PGPMessage +import com.proton.gopenpgp.crypto.PGPSignature import com.proton.gopenpgp.crypto.PlainMessage import com.proton.gopenpgp.crypto.SessionKey import com.squareup.inject.assisted.Assisted @@ -73,9 +75,6 @@ class AddressCrypto @AssistedInject constructor( override val passphrase: ByteArray? get() = passphraseFor(requirePrimaryKey()) - private val AddressKey.isPrimary get() = - this == addressKeys.primaryKey - @Suppress("EXTENSION_SHADOWED_BY_MEMBER") protected override val AddressKey.privateKey: PgpField.PrivateKey get() = privateKey @@ -85,43 +84,51 @@ class AddressCrypto @AssistedInject constructor( val token = key.token val signature = key.signature - return if (token == null || signature == null) { - mailboxPassword - - } else { - val errorMessage = "Failed getting passphrase for key ${key.id.s}, " + - "primary = ${key.isPrimary}, " + - "has activation = ${key.activation != null}" - - val armoredPrivateKey = userManager.getTokenManager(userManager.username)?.encPrivateKey - armoredPrivateKey?.let { - val decryptedToken = openPgp.decryptMessage(token.string, armoredPrivateKey, mailboxPassword) - val validSignature = verifySignature(it, decryptedToken, signature.string, errorMessage) - require(validSignature) + if (token == null || signature == null) { + return mailboxPassword + } - decryptedToken.toByteArray(Charsets.UTF_8) + val pgpMessage = GoOpenPgpCrypto.newPGPMessageFromArmored(token.string) + userManager.user.keys.forEach { userKey -> + val userKeyRing = GoOpenPgpCrypto.newKeyRing( + GoOpenPgpCrypto.newKeyFromArmored(userKey.privateKey).unlock(mailboxPassword) + ) + decryptToken( + pgpMessage, + userKeyRing + )?.let { decryptedToken -> + if (verifySignature(userKeyRing, decryptedToken, signature, userKey.id, key.id.s)) { + return decryptedToken + } } } + Timber.e("Failed getting passphrase for key (id = ${key.id.s}) using user keys") + return null } + private fun decryptToken( + token: PGPMessage, + userKeyRing: KeyRing + ): ByteArray? = runCatching { + userKeyRing.decrypt(token, null, 0).data + }.getOrNull() + private fun verifySignature( - armoredPrivateKey: String, - decryptedToken: String, - signature: String, - errorMessage: String + userKeyRing: KeyRing, + decryptedToken: ByteArray, + signature: PgpField.Signature, + userKeyId: String, + addressKeyId: String ) = runCatching { - val armoredSignature = GoOpenPgpCrypto.newPGPSignatureFromArmored(signature) - val unlockedArmoredKey = GoOpenPgpCrypto.newKeyFromArmored(armoredPrivateKey).unlock(mailboxPassword) - val verificationKeyRing = GoOpenPgpCrypto.newKeyRing(unlockedArmoredKey) - verificationKeyRing.verifyDetached( + userKeyRing.verifyDetached( PlainMessage(decryptedToken), - armoredSignature, + PGPSignature(signature.string), GoOpenPgpCrypto.getUnixTime() ) }.fold( onSuccess = { true }, onFailure = { - Timber.w(it, errorMessage) + Timber.e(it, "Verification of token for address key (id = $addressKeyId) with user key (id = $userKeyId) failed") false } ) From fd43b77f1ee26a02ddc62f4edd7a0dea14a5f0db Mon Sep 17 00:00:00 2001 From: Zorica Stojchevska Date: Tue, 12 Oct 2021 14:51:25 +0200 Subject: [PATCH 08/11] Changed build version #comment Just increased the build version name and code for a new release Affected: nothing --- buildSrc/src/main/kotlin/ProtonMail.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/kotlin/ProtonMail.kt b/buildSrc/src/main/kotlin/ProtonMail.kt index aea120676..ec0493750 100644 --- a/buildSrc/src/main/kotlin/ProtonMail.kt +++ b/buildSrc/src/main/kotlin/ProtonMail.kt @@ -22,8 +22,9 @@ * @author Davide Farella */ object ProtonMail { - const val versionName = "1.13.35" - const val versionCode = 782 + + const val versionName = "1.13.36" + const val versionCode = 783 const val targetSdk = 30 const val minSdk = 21 From a84ffa0258afc7e0c9f48b0970a607d03481b90c Mon Sep 17 00:00:00 2001 From: Zorica Stojchevska Date: Fri, 15 Oct 2021 17:00:25 +0200 Subject: [PATCH 09/11] Disabled the spinner to not allow user to choose any domain other than `protonmail.com` on signup --- .../fragments/CreateAccountFragment.java | 2 ++ .../android/core/ProtonMailApplication.java | 22 ++++++++----------- .../res/layout/fragment_create_account.xml | 1 + 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/ch/protonmail/android/activities/fragments/CreateAccountFragment.java b/app/src/main/java/ch/protonmail/android/activities/fragments/CreateAccountFragment.java index cb85dba70..a4ab6ef8d 100644 --- a/app/src/main/java/ch/protonmail/android/activities/fragments/CreateAccountFragment.java +++ b/app/src/main/java/ch/protonmail/android/activities/fragments/CreateAccountFragment.java @@ -151,6 +151,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa mRootLayout.getViewTreeObserver().addOnGlobalLayoutListener(this); ArrayAdapter adapter = new ArrayAdapter<>(getContext(),R.layout.simple_spinner_item, domains); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + mDomainsSpinner.setEnabled(false); mDomainsSpinner.setAdapter(adapter); mDomainsSpinner.setOnItemSelectedListener(this); setUsernameEditTextPadding(); @@ -197,6 +198,7 @@ public void setDomains(List availableDomains) { } ArrayAdapter adapter = new ArrayAdapter<>(getContext(),R.layout.simple_spinner_item, domains); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + mDomainsSpinner.setEnabled(false); mDomainsSpinner.setAdapter(adapter); setUsernameEditTextPadding(); } diff --git a/app/src/main/java/ch/protonmail/android/core/ProtonMailApplication.java b/app/src/main/java/ch/protonmail/android/core/ProtonMailApplication.java index a7ff8598e..ac5534559 100644 --- a/app/src/main/java/ch/protonmail/android/core/ProtonMailApplication.java +++ b/app/src/main/java/ch/protonmail/android/core/ProtonMailApplication.java @@ -18,6 +18,15 @@ */ package ch.protonmail.android.core; +import static ch.protonmail.android.api.segments.event.EventManagerKt.PREF_LATEST_EVENT; +import static ch.protonmail.android.core.Constants.FCM_MIGRATION_VERSION; +import static ch.protonmail.android.core.Constants.Prefs.PREF_SENT_TOKEN_TO_SERVER; +import static ch.protonmail.android.core.Constants.Prefs.PREF_TIME_AND_DATE_CHANGED; +import static ch.protonmail.android.core.UserManagerKt.LOGIN_STATE_TO_INBOX; +import static ch.protonmail.android.core.UserManagerKt.PREF_LOGIN_STATE; +import static ch.protonmail.android.core.UserManagerKt.PREF_SHOW_STORAGE_LIMIT_REACHED; +import static ch.protonmail.android.core.UserManagerKt.PREF_SHOW_STORAGE_LIMIT_WARNING; + import android.app.Activity; import android.app.AlertDialog; import android.app.Application; @@ -119,15 +128,6 @@ import studio.forface.viewstatestore.ViewStateStoreConfig; import timber.log.Timber; -import static ch.protonmail.android.api.segments.event.EventManagerKt.PREF_LATEST_EVENT; -import static ch.protonmail.android.core.Constants.FCM_MIGRATION_VERSION; -import static ch.protonmail.android.core.Constants.Prefs.PREF_SENT_TOKEN_TO_SERVER; -import static ch.protonmail.android.core.Constants.Prefs.PREF_TIME_AND_DATE_CHANGED; -import static ch.protonmail.android.core.UserManagerKt.LOGIN_STATE_TO_INBOX; -import static ch.protonmail.android.core.UserManagerKt.PREF_LOGIN_STATE; -import static ch.protonmail.android.core.UserManagerKt.PREF_SHOW_STORAGE_LIMIT_REACHED; -import static ch.protonmail.android.core.UserManagerKt.PREF_SHOW_STORAGE_LIMIT_WARNING; - @HiltAndroidApp public class ProtonMailApplication extends Application implements androidx.work.Configuration.Provider { @@ -163,7 +163,6 @@ public class ProtonMailApplication extends Application implements androidx.work. private boolean mUpdateOccurred; private AllCurrencyPlans mAllCurrencyPlans; private Organization mOrganization; - private List mAvailableDomains; private String mCurrentLocale; private boolean mChangedSystemTimeDate; private AlertDialog forceUpgradeDialog; @@ -400,9 +399,6 @@ public void onPasswordChangeEvent(PasswordChangeEvent event) { @Subscribe public void onAvailableDomainsEvent(AvailableDomainsEvent event) { - if (event.getStatus() == Status.SUCCESS) { - this.mAvailableDomains = event.getDomains(); - } } @Subscribe diff --git a/app/src/main/res/layout/fragment_create_account.xml b/app/src/main/res/layout/fragment_create_account.xml index a52de433f..02058c469 100644 --- a/app/src/main/res/layout/fragment_create_account.xml +++ b/app/src/main/res/layout/fragment_create_account.xml @@ -156,6 +156,7 @@ along with ProtonMail. If not, see https://www.gnu.org/licenses/. android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" + android:background="@null" android:layout_marginTop="8dp"/> From 6186d1dd83344b1ddfc786f78e9d879ae2838993 Mon Sep 17 00:00:00 2001 From: Zorica Stojchevska Date: Wed, 20 Oct 2021 08:46:58 +0200 Subject: [PATCH 10/11] Fixes Portuguese translation --- app/src/main/res/values-pt/strings.xml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 26171f8ba..8df454cc5 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -71,14 +71,14 @@ along with ProtonMail. If not, see https://www.gnu.org/licenses/. ENVIADO ARQUIVO LIXO - ASSÉDIO + Spam Caixa de entrada Com estrela Rascunhos Enviado Arquivo Lixo - Assédio + Spam Contactos Procurar contactos A obter contactos do ProtonMail... @@ -101,7 +101,7 @@ along with ProtonMail. If not, see https://www.gnu.org/licenses/. Marcar como não lida Mover para o arquivo Mover para o lixo - Mover para o assédio + Mover para Spam Mover para a caixa de entrada Tem a certeza de que deseja eliminar todas as mensagens desta pasta? Enviar @@ -394,11 +394,11 @@ along with ProtonMail. If not, see https://www.gnu.org/licenses/. Prioridade no apoio ao cliente Actualizar Opção de pagamento - Para planos com mais armazenamento, actualize através + Para planos com mais armazenamento, actualize através da aplicação web do ProtonMail. Obrigado por ser um utilizador pagante! Seleccione o seu tipo de conta - O ProtonMail é um serviço gratuito + O ProtonMail é um serviço gratuito para o bem público. Pode ajudar a apoiar a privacidade online subscrevendo uma conta paga. FREE PLUS %s /mês @@ -730,9 +730,9 @@ along with ProtonMail. If not, see https://www.gnu.org/licenses/. Imprimir Confirmar relatório de phishing Comunicar uma mensagem como uma tentativa de phishing enviará a - mensagem para nós, para que possamos analisá-la e melhorar os nossos filtros. Isto significa que poderemos + mensagem para nós, para que possamos analisá-la e melhorar os nossos filtros. Isto significa que poderemos ver o conteúdo da mensagem na íntegra. - Relatório enviado. Mensagem movida para o assédio. + Relatório enviado. Mensagem movida para o spam. Impossível enviar o relatório ContactGroupDetailsActivity @@ -897,7 +897,7 @@ along with ProtonMail. If not, see https://www.gnu.org/licenses/. Mensagem movida para o lixo Movida para o lixo Estrela da mensagem actualizada - Mensagem marcada como assédio + Mensagem marcada como spam Mensagem arquivada Mensagem marcada como lida @@ -905,8 +905,8 @@ along with ProtonMail. If not, see https://www.gnu.org/licenses/. Mensagens movidas para o lixo - Mensagem marcada como assédio - Mensagens marcadas como assédio + Mensagem marcada como spam + Mensagens marcadas como spam Mensagem arquivada From e6188744a0a3847aff7ff3e11ba1739e9c3b3a05 Mon Sep 17 00:00:00 2001 From: Zorica Stojchevska Date: Wed, 20 Oct 2021 11:47:37 +0200 Subject: [PATCH 11/11] Changed build version #comment Just increased the build version name and code for a new release Affected: nothing --- buildSrc/src/main/kotlin/ProtonMail.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/kotlin/ProtonMail.kt b/buildSrc/src/main/kotlin/ProtonMail.kt index ec0493750..edf3be207 100644 --- a/buildSrc/src/main/kotlin/ProtonMail.kt +++ b/buildSrc/src/main/kotlin/ProtonMail.kt @@ -23,8 +23,8 @@ */ object ProtonMail { - const val versionName = "1.13.36" - const val versionCode = 783 + const val versionName = "1.13.37" + const val versionCode = 784 const val targetSdk = 30 const val minSdk = 21