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/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..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.retryPingAsPreviousRequestWasInconclusive() } 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..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 @@ -55,7 +55,8 @@ class ProtonMailRequestInterceptor private constructor( } catch (exception: IOException) { Timber.d(exception, "Intercept: IOException with url: " + request.url()) - networkUtils.retryPingAsPreviousRequestWasInconclusive() + AppUtil.postEventOnUi(ConnectivityEvent(false)) + networkUtils.setConnectivityHasFailed(exception) } requestCount-- 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/main/java/ch/protonmail/android/api/segments/event/FetchUpdatesJob.java b/app/src/main/java/ch/protonmail/android/api/segments/event/FetchUpdatesJob.java new file mode 100644 index 000000000..52502a7a4 --- /dev/null +++ b/app/src/main/java/ch/protonmail/android/api/segments/event/FetchUpdatesJob.java @@ -0,0 +1,83 @@ +/* + * 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 androidx.annotation.Nullable; + +import com.birbit.android.jobqueue.Params; + +import java.net.ConnectException; +import java.util.List; +import java.util.concurrent.TimeUnit; + +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()) { + 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 + long currentTime = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()); + messagesDatabase.deleteExpiredMessages(currentTime); + try { + 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 + 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/segments/event/FetchUpdatesJob.kt b/app/src/main/java/ch/protonmail/android/api/segments/event/FetchUpdatesJob.kt deleted file mode 100644 index 53e621f30..000000000 --- a/app/src/main/java/ch/protonmail/android/api/segments/event/FetchUpdatesJob.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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.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 - -class FetchUpdatesJob internal constructor(private val eventManager: EventManager) : ProtonMailBaseJob( - Params(Priority.HIGH).requireNetwork() -) { - - constructor() : this(ProtonMailApplication.getApplication().eventManager) - - @Throws(Throwable::class) - override fun onRun() { - val messageDao = MessagesDatabaseFactory.getInstance(applicationContext).getDatabase() - if (!getQueueNetworkUtil().isConnected()) { - Timber.i("no network cannot fetch updates") - return - } - - // check for expired messages in the cache and delete them - val currentTime = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) - messageDao.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() - } - } - } - - override fun onProtonCancel(cancelReason: Int, throwable: Throwable?) {} -} 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/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/java/ch/protonmail/android/core/QueueNetworkUtil.kt b/app/src/main/java/ch/protonmail/android/core/QueueNetworkUtil.kt index 59dac0075..1f9eb32c3 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,6 @@ class QueueNetworkUtil @Inject constructor( fun isConnected(): Boolean = hasConn(false) fun setCurrentlyHasConnectivity() = updateRealConnectivity(true) - fun retryPingAsPreviousRequestWasInconclusive() = - updateRealConnectivity(false, Constants.ConnectionState.PING_NEEDED) fun setConnectivityHasFailed(throwable: Throwable) { // for valid failure types specified below @@ -113,7 +108,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 +136,8 @@ class QueueNetworkUtil @Inject constructor( } } return hasConnection + + } } 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 } ) 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..a6b834080 100644 --- a/app/src/main/java/ch/protonmail/android/usecase/VerifyConnection.kt +++ b/app/src/main/java/ch/protonmail/android/usecase/VerifyConnection.kt @@ -74,7 +74,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/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}") 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..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 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"/> 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 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) + ) +} 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 diff --git a/buildSrc/src/main/kotlin/ProtonMail.kt b/buildSrc/src/main/kotlin/ProtonMail.kt index 75656f654..edf3be207 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.34" - const val versionCode = 780 + + const val versionName = "1.13.37" + const val versionCode = 784 const val targetSdk = 30 const val minSdk = 21