diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
index a5f05cd8c8..e34606ccde 100644
--- a/.idea/jarRepositories.xml
+++ b/.idea/jarRepositories.xml
@@ -21,5 +21,10 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml
index 9f7f4138cd..e9de20d3b3 100644
--- a/.semaphore/semaphore.yml
+++ b/.semaphore/semaphore.yml
@@ -23,7 +23,7 @@ global_job_config:
value: /home/semaphore/Android/Sdk
prologue:
commands:
- - export PATH=${ANDROID_SDK_ROOT}/emulator:${ANDROID_SDK_ROOT}/tools:${ANDROID_SDK_ROOT}/tools/bin:${ANDROID_SDK_ROOT}/platform-tools:${PATH}
+ - export PATH=${ANDROID_SDK_ROOT}/emulator:${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin:${ANDROID_SDK_ROOT}/platform-tools:${PATH}
- sudo rm -rf ~/.rbenv ~/.phpbrew
- checkout
# restore global caches
@@ -51,6 +51,7 @@ blocks:
- name: 'Build Project'
commands:
- cat /proc/cpuinfo # print debug info
+ - java -version # print Java version
- ./gradlew assembleDevTestDebug # compile project
- ./script/ci-lint-checks.sh # run Lint checks
epilogue:
diff --git a/FlowCrypt/build.gradle b/FlowCrypt/build.gradle
index e64df9f462..cf206c3fbb 100644
--- a/FlowCrypt/build.gradle
+++ b/FlowCrypt/build.gradle
@@ -9,7 +9,7 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: "androidx.navigation.safeargs.kotlin"
-apply plugin: 'com.akaita.android.easylauncher'
+apply plugin: "com.starter.easylauncher"
def keystoreProperties = new Properties()
File propertiesFile = project.file("keystore.properties")
@@ -239,12 +239,15 @@ android {
lintOptions {
warningsAsErrors true
- //we disable 'IconLauncherShape' to prevent fails because of use easylauncher
- disable 'NullSafeMutableLiveData', 'IconLauncherShape'
}
packagingOptions {
exclude 'META-INF/DEPENDENCIES'
+ exclude 'META-INF/LICENSE.md'
+ exclude 'META-INF/NOTICE.md'
+ exclude 'META-INF/*.SF'
+ exclude 'META-INF/*.DSA'
+ exclude 'META-INF/*.RSA'
}
testOptions {
@@ -300,12 +303,10 @@ android {
}
easylauncher {
- iconNames "@mipmap/ic_launcher_foreground"
- foregroundIconNames "@mipmap/ic_launcher_foreground" // Foreground of adaptive launcher icon
buildTypes {
debug {
filters = [
- customColorRibbonFilter("debug", "#556600CC", "#FFFFFF", "top")
+ customRibbon(ribbonColor: "#6600CC", labelColor: "#FFFFFF", position: "top")
]
}
}
@@ -313,7 +314,7 @@ easylauncher {
variants {
devDebug {
filters = [
- customColorRibbonFilter("dev", "#55CC5F00", "#FFFFFF", "top")
+ customRibbon(label: "dev", ribbonColor: "#CC5F00", labelColor: "#FFFFFF", position: "top")
]
}
}
@@ -321,7 +322,7 @@ easylauncher {
dependencies {
kapt "com.github.bumptech.glide:compiler:${rootProject.ext.glideVersion}"
- kapt "androidx.annotation:annotation:${rootProject.ext.androidxBaseVersion}"
+ kapt 'androidx.annotation:annotation:1.1.0'
kapt "androidx.room:room-compiler:$roomVersion"
debugImplementation "com.squareup.leakcanary:leakcanary-android:${rootProject.ext.leakcanaryVersion}"
@@ -356,12 +357,12 @@ dependencies {
implementation "androidx.cardview:cardview:${rootProject.ext.androidxBaseVersion}"
implementation 'androidx.browser:browser:1.3.0'
implementation "androidx.recyclerview:recyclerview:${rootProject.ext.recyclerViewVersion}"
- implementation "androidx.recyclerview:recyclerview-selection:${rootProject.ext.recyclerViewVersion}-rc03"
+ implementation "androidx.recyclerview:recyclerview-selection:${rootProject.ext.recyclerViewVersion}"
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation "androidx.test.espresso:espresso-idling-resource:${rootProject.ext.espressoVersion}"
implementation "androidx.lifecycle:lifecycle-extensions:${rootProject.ext.lifecycleVersion}"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:${rootProject.ext.lifecycleVersion}"
- implementation "androidx.lifecycle:lifecycle-livedata-ktx:${rootProject.ext.lifecycleVersion}"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.0"
implementation "androidx.room:room-runtime:$roomVersion"
implementation "androidx.room:room-ktx:$roomVersion"
implementation "androidx.paging:paging-runtime-ktx:$pagingVersion"
@@ -396,6 +397,7 @@ dependencies {
implementation "com.squareup.leakcanary:leakcanary-object-watcher-android:${rootProject.ext.leakcanaryVersion}"
implementation "com.sun.mail:android-mail:${rootProject.ext.javaMailVersion}"
+ implementation "com.sun.mail:android-activation:${rootProject.ext.javaMailVersion}"
implementation("com.sun.mail:gimap:${rootProject.ext.javaMailVersion}") {
//exclude group: 'com.sun.mail' because it exists in 'com.sun.mail:android-mail'
exclude group: 'com.sun.mail'
@@ -404,13 +406,19 @@ dependencies {
implementation "com.github.bumptech.glide:glide:${rootProject.ext.glideVersion}"
implementation 'com.hootsuite.android:nachos:1.2.0'
implementation 'com.nulab-inc:zxcvbn:1.4.0'
- implementation 'com.madgag.spongycastle:pkix:1.54.0.0'
- implementation 'com.madgag.spongycastle:pg:1.54.0.0'
implementation 'commons-io:commons-io:2.8.0'
implementation 'ch.acra:acra:4.11.1'
implementation 'ja.burhanrashid52:photoeditor:1.1.0'
implementation "org.jetbrains.kotlin:kotlin-reflect:1.4.31"
- implementation 'net.openid:appauth:0.7.1-14-ged7e194'
+ implementation 'net.openid:appauth:0.8.0'
implementation 'org.bitbucket.b_c:jose4j:0.7.6'
implementation 'me.everything:overscroll-decor-android:1.1.0'
+
+ implementation ('org.pgpainless:pgpainless-core:0.2.0-alpha8') {
+ //exclude group: 'org.bouncycastle' because we will specify it manually
+ exclude group: 'org.bouncycastle'
+ }
+
+ implementation "org.bouncycastle:bcpg-jdk15on:${rootProject.ext.bouncycastleVersion}"
+ implementation "org.bouncycastle:bcpkix-jdk15on:${rootProject.ext.bouncycastleVersion}"
}
diff --git a/FlowCrypt/lint.xml b/FlowCrypt/lint.xml
index 11b8307451..e711fecc96 100644
--- a/FlowCrypt/lint.xml
+++ b/FlowCrypt/lint.xml
@@ -2,7 +2,7 @@
~ © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com
~ Contributors: DenBond7
-->
-
+
@@ -14,6 +14,7 @@
+
diff --git a/FlowCrypt/proguard-rules.pro b/FlowCrypt/proguard-rules.pro
index 5da352d2a3..fcc2faca5f 100644
--- a/FlowCrypt/proguard-rules.pro
+++ b/FlowCrypt/proguard-rules.pro
@@ -296,10 +296,10 @@
-keep interface org.w3c.dom.** { *; }
-dontwarn org.w3c.dom.**
-########################################## SPONGYCASTLE ###############################################################
--keep class org.spongycastle.** { *; }
--keep interface org.spongycastle.** { *; }
--dontwarn org.spongycastle.**
+########################################## BOUNCYCASTLE ###############################################################
+-keep class org.bouncycastle.** { *; }
+-keep interface org.bouncycastle.** { *; }
+-dontwarn org.bouncycastle.**
########################################## Play services ##############################################################
-dontnote com.google.android.gms.**
diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/DebugTestingTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/DebugTestingTest.kt
index 57b8311459..74682c7673 100644
--- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/DebugTestingTest.kt
+++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/DebugTestingTest.kt
@@ -21,14 +21,10 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class DebugTestingTest {
@Test
- @DoesNotNeedMailserver
- @ReadyForCIAnnotation
fun alwaysSuccessTest() {
}
@Test
- @DoesNotNeedMailserver
- @ReadyForCIAnnotation
fun alwaysSuccessTestSecond() {
}
}
\ No newline at end of file
diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/junit/filters/DependsOnMailServerFilter.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/junit/filters/DependsOnMailServerFilter.kt
index 6302df6494..38fc8a2a29 100644
--- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/junit/filters/DependsOnMailServerFilter.kt
+++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/junit/filters/DependsOnMailServerFilter.kt
@@ -15,8 +15,11 @@ import org.junit.runner.Description
* E-mail: DenBond7@gmail.com
*/
class DependsOnMailServerFilter : ReadyForCIFilter() {
- override fun shouldRun(description: Description?): Boolean {
- return description?.isTest == false || (super.shouldRun(description) && description?.getAnnotation(DoesNotNeedMailserver::class.java) == null)
+ override fun evaluateTest(description: Description?): Boolean {
+ val annotationClass = DoesNotNeedMailserver::class.java
+ val hasClassAnnotation = description?.testClass?.isAnnotationPresent(annotationClass) == true
+ if (hasClassAnnotation) return false
+ return super.evaluateTest(description) && description?.getAnnotation(annotationClass) == null
}
override fun describe() = "Filter tests that depend on an email server"
diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/junit/filters/DoesNotNeedMailServerFilter.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/junit/filters/DoesNotNeedMailServerFilter.kt
index 728ea0af6b..88c7e66e72 100644
--- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/junit/filters/DoesNotNeedMailServerFilter.kt
+++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/junit/filters/DoesNotNeedMailServerFilter.kt
@@ -15,8 +15,11 @@ import org.junit.runner.Description
* E-mail: DenBond7@gmail.com
*/
class DoesNotNeedMailServerFilter : ReadyForCIFilter() {
- override fun shouldRun(description: Description?): Boolean {
- return description?.isTest == false || (super.shouldRun(description) && description?.getAnnotation(DoesNotNeedMailserver::class.java) != null)
+ override fun evaluateTest(description: Description?): Boolean {
+ val annotationClass = DoesNotNeedMailserver::class.java
+ return super.evaluateTest(description)
+ && (description?.testClass?.isAnnotationPresent(annotationClass) == true
+ || description?.getAnnotation(annotationClass) != null)
}
override fun describe() = "Filter tests that don't need an email server"
diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/junit/filters/OtherTestsFilter.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/junit/filters/OtherTestsFilter.kt
index 76ff11955d..de71070801 100644
--- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/junit/filters/OtherTestsFilter.kt
+++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/junit/filters/OtherTestsFilter.kt
@@ -5,8 +5,8 @@
package com.flowcrypt.email.junit.filters
+import androidx.test.internal.runner.filters.ParentFilter
import org.junit.runner.Description
-import org.junit.runner.manipulation.Filter
/**
* @author Denis Bondarenko
@@ -14,12 +14,12 @@ import org.junit.runner.manipulation.Filter
* Time: 10:42 AM
* E-mail: DenBond7@gmail.com
*/
-class OtherTestsFilter : Filter() {
+class OtherTestsFilter : ParentFilter() {
private val dependsOnMailServerFilter = DependsOnMailServerFilter()
private val doesNotNeedMailServerFilter = DoesNotNeedMailServerFilter()
- override fun shouldRun(description: Description?): Boolean {
- return description?.isTest == false || (!dependsOnMailServerFilter.shouldRun(description) && !doesNotNeedMailServerFilter.shouldRun(description))
+ override fun evaluateTest(description: Description?): Boolean {
+ return (!dependsOnMailServerFilter.shouldRun(description) && !doesNotNeedMailServerFilter.shouldRun(description))
}
override fun describe() = "Filter tests that are not related to any conditions"
diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/junit/filters/ReadyForCIFilter.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/junit/filters/ReadyForCIFilter.kt
index 66abf7f7bd..8dbfd777a5 100644
--- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/junit/filters/ReadyForCIFilter.kt
+++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/junit/filters/ReadyForCIFilter.kt
@@ -5,9 +5,9 @@
package com.flowcrypt.email.junit.filters
+import androidx.test.internal.runner.filters.ParentFilter
import com.flowcrypt.email.ReadyForCIAnnotation
import org.junit.runner.Description
-import org.junit.runner.manipulation.Filter
/**
* @author Denis Bondarenko
@@ -15,9 +15,10 @@ import org.junit.runner.manipulation.Filter
* Time: 5:24 PM
* E-mail: DenBond7@gmail.com
*/
-open class ReadyForCIFilter : Filter() {
- override fun shouldRun(description: Description?): Boolean {
- return description?.getAnnotation(ReadyForCIAnnotation::class.java) != null
+open class ReadyForCIFilter : ParentFilter() {
+ override fun evaluateTest(description: Description?): Boolean {
+ val annotationClass = ReadyForCIAnnotation::class.java
+ return (description?.testClass?.isAnnotationPresent(annotationClass) == true || description?.getAnnotation(annotationClass) != null)
}
override fun describe() = "Filter tests that are ready to be run on a CI server"
diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/enterprise/SignInActivityEnterpriseTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/enterprise/SignInActivityEnterpriseTest.kt
index c2b48b7780..44554cc66c 100644
--- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/enterprise/SignInActivityEnterpriseTest.kt
+++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/enterprise/SignInActivityEnterpriseTest.kt
@@ -15,6 +15,7 @@ import androidx.test.ext.junit.rules.activityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import androidx.test.platform.app.InstrumentationRegistry
+import com.flowcrypt.email.CICandidateAnnotation
import com.flowcrypt.email.DoesNotNeedMailserver
import com.flowcrypt.email.R
import com.flowcrypt.email.ReadyForCIAnnotation
@@ -99,7 +100,7 @@ class SignInActivityEnterpriseTest : BaseSignActivityTest() {
}
@Test
- @ReadyForCIAnnotation
+ @CICandidateAnnotation
fun testNoPrvCreateRule() {
setupAndClickSignInButton(genMockGoogleSignInAccountJson(EMAIL_WITH_NO_PRV_CREATE_RULE))
intended(hasComponent(CreateOrImportKeyActivity::class.java.name))
diff --git a/FlowCrypt/src/main/AndroidManifest.xml b/FlowCrypt/src/main/AndroidManifest.xml
index d5afdf9313..39c1087bdf 100644
--- a/FlowCrypt/src/main/AndroidManifest.xml
+++ b/FlowCrypt/src/main/AndroidManifest.xml
@@ -30,7 +30,6 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
- android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/AppTheme.NoActionBar"
tools:replace="android:allowBackup">
diff --git a/FlowCrypt/src/main/ic_launcher-playstore.png b/FlowCrypt/src/main/ic_launcher-playstore.png
new file mode 100644
index 0000000000..629a8faaab
Binary files /dev/null and b/FlowCrypt/src/main/ic_launcher-playstore.png differ
diff --git a/FlowCrypt/src/main/ic_launcher-web.png b/FlowCrypt/src/main/ic_launcher-web.png
deleted file mode 100644
index 830fe6d510..0000000000
Binary files a/FlowCrypt/src/main/ic_launcher-web.png and /dev/null differ
diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/email/FoldersManager.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/email/FoldersManager.kt
index 67cd6c01eb..a4a455a800 100644
--- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/email/FoldersManager.kt
+++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/email/FoldersManager.kt
@@ -234,25 +234,19 @@ class FoldersManager constructor(val account: String) {
val sortedList = arrayOfNulls(localFolders.size)
val inbox = folderInbox?.let {
- localFolders.remove(it)
sortedList[0] = it.folderAlias
- it
- }
-
- folderTrash?.let {
localFolders.remove(it)
- sortedList[localFolders.size + 1] = it.folderAlias
+ it
}
- folderSpam?.let {
- localFolders.remove(it)
- sortedList[localFolders.size + 1] = it.folderAlias
+ val moveFolder = fun(localFolder: LocalFolder) {
+ sortedList[localFolders.size] = localFolder.folderAlias
+ localFolders.remove(localFolder)
}
- folderOutbox?.let {
- localFolders.remove(it)
- sortedList[localFolders.size + 1] = it.folderAlias
- }
+ folderTrash?.let { moveFolder(it) }
+ folderSpam?.let { moveFolder(it) }
+ folderOutbox?.let { moveFolder(it) }
for (i in localFolders.indices) {
val localFolder = localFolders[i]
diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/node/NodeRetrofitHelper.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/node/NodeRetrofitHelper.kt
index 791c84658b..6d509b4058 100644
--- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/node/NodeRetrofitHelper.kt
+++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/node/NodeRetrofitHelper.kt
@@ -13,15 +13,14 @@ import com.flowcrypt.email.Constants
import com.flowcrypt.email.api.retrofit.node.gson.NodeGson
import com.flowcrypt.email.node.NodeSecret
import com.flowcrypt.email.util.GeneralUtil
-import com.flowcrypt.email.util.LogsUtil
import com.flowcrypt.email.util.SharedPreferencesHelper
import com.google.gson.Gson
import okhttp3.Interceptor
import okhttp3.OkHttpClient
-import okhttp3.Response
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import java.security.cert.X509Certificate
+import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import javax.net.ssl.HostnameVerifier
@@ -37,6 +36,7 @@ import javax.net.ssl.HostnameVerifier
object NodeRetrofitHelper {
private const val TIMEOUT = 90
private var okHttpClient: OkHttpClient? = null
+ private val countDownLatch: CountDownLatch = CountDownLatch(1)
@Volatile
private var retrofit: Retrofit? = null
@@ -52,11 +52,15 @@ object NodeRetrofitHelper {
.client(okHttpClient!!)
retrofit = retrofitBuilder.build()
+ countDownLatch.countDown()
}
@JvmStatic
fun getRetrofit(): Retrofit? {
- checkAndWaitNode()
+ if (retrofit == null) {
+ countDownLatch.await(60, TimeUnit.SECONDS)
+ }
+
return retrofit
}
@@ -90,40 +94,17 @@ object NodeRetrofitHelper {
return builder
}
- /**
- * This method does 5 attempts to check is Node.js server started.
- */
- private fun checkAndWaitNode() {
- var attemptCount = 5
-
- while (attemptCount != 0) {
- if (retrofit == null) {
- attemptCount--
- try {
- LogsUtil.d(NodeRetrofitHelper::class.java.simpleName, "Node.js server is not run yet. Trying to wait...")
- Thread.sleep(1000)
- } catch (e: InterruptedException) {
- e.printStackTrace()
- }
- } else {
- return
- }
- }
- }
-
private fun headersInterceptor(nodeSecret: NodeSecret): Interceptor {
- return object : Interceptor {
- override fun intercept(chain: Interceptor.Chain): Response {
- var request: okhttp3.Request = chain.request()
- val headers = request
- .headers
- .newBuilder()
- .add("Authorization", nodeSecret.authHeader)
- .add("Connection", "Keep-Alive")
- .build()
- request = request.newBuilder().headers(headers).build()
- return chain.proceed(request)
- }
+ return Interceptor { chain ->
+ var request: okhttp3.Request = chain.request()
+ val headers = request
+ .headers
+ .newBuilder()
+ .add("Authorization", nodeSecret.authHeader)
+ .add("Connection", "Keep-Alive")
+ .build()
+ request = request.newBuilder().headers(headers).build()
+ chain.proceed(request)
}
}
diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/database/dao/AccountDao.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/database/dao/AccountDao.kt
index 80765cc6b4..6252c66591 100644
--- a/FlowCrypt/src/main/java/com/flowcrypt/email/database/dao/AccountDao.kt
+++ b/FlowCrypt/src/main/java/com/flowcrypt/email/database/dao/AccountDao.kt
@@ -112,6 +112,14 @@ abstract class AccountDao : BaseDao {
updateAccountSuspend(existedAccount.copy(isActive = true))
}
+ @Transaction
+ open suspend fun logout(accountEntity: AccountEntity) {
+ deleteSuspend(accountEntity)
+ getAllNonactiveAccountsSuspend().firstOrNull()?.let {
+ switchAccountSuspend(it)
+ }
+ }
+
@Transaction
open fun updateAccount(entity: AccountEntity): Int {
val existedAccount = getAccount(entity.email) ?: return 0
diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/DecryptMessageViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/DecryptMessageViewModel.kt
index f6fd46f033..30eaf0f619 100644
--- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/DecryptMessageViewModel.kt
+++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/DecryptMessageViewModel.kt
@@ -30,7 +30,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.apache.commons.io.IOUtils
-import org.spongycastle.bcpg.ArmoredInputStream
+import org.bouncycastle.bcpg.ArmoredInputStream
import java.io.ByteArrayInputStream
import java.io.File
import java.io.FileOutputStream
diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/MsgDetailsViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/MsgDetailsViewModel.kt
index cd041e9265..450df38739 100644
--- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/MsgDetailsViewModel.kt
+++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/MsgDetailsViewModel.kt
@@ -59,7 +59,7 @@ import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.apache.commons.io.IOUtils
-import org.spongycastle.bcpg.ArmoredInputStream
+import org.bouncycastle.bcpg.ArmoredInputStream
import java.io.BufferedInputStream
import java.io.ByteArrayInputStream
import java.io.File
diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/workmanager/BaseWorker.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/workmanager/BaseWorker.kt
index d1c2813cb2..c8d391bc83 100644
--- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/workmanager/BaseWorker.kt
+++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/workmanager/BaseWorker.kt
@@ -8,6 +8,10 @@ package com.flowcrypt.email.jetpack.workmanager
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
+import com.flowcrypt.email.database.FlowCryptRoomDatabase
+import com.flowcrypt.email.database.entity.AccountEntity
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
/**
* @author Denis Bondarenko
@@ -17,4 +21,15 @@ import androidx.work.WorkerParameters
*/
abstract class BaseWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
abstract val useIndependentConnection: Boolean
+ protected val roomDatabase = FlowCryptRoomDatabase.getDatabase(applicationContext)
+
+ suspend fun rescheduleIfActiveAccountWasChanged(accountEntity: AccountEntity?): Result = withContext(Dispatchers.IO) {
+ val activeAccountEntity = roomDatabase.accountDao().getActiveAccountSuspend()
+ if (activeAccountEntity?.id == accountEntity?.id) {
+ return@withContext Result.success()
+ } else {
+ //reschedule a task if the active account was changed
+ return@withContext Result.retry()
+ }
+ }
}
\ No newline at end of file
diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/workmanager/ForwardedAttachmentsDownloaderWorker.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/workmanager/ForwardedAttachmentsDownloaderWorker.kt
index 3d21be2b79..9aa394b86a 100644
--- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/workmanager/ForwardedAttachmentsDownloaderWorker.kt
+++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/workmanager/ForwardedAttachmentsDownloaderWorker.kt
@@ -59,9 +59,10 @@ import javax.mail.Store
* Time: 11:48
* E-mail: DenBond7@gmail.com
*/
-class ForwardedAttachmentsDownloaderWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
+class ForwardedAttachmentsDownloaderWorker(context: Context, params: WorkerParameters) : BaseWorker(context, params) {
private val attCacheDir = File(applicationContext.cacheDir, Constants.ATTACHMENTS_CACHE_DIR)
private val fwdAttsCacheDir = File(attCacheDir, Constants.FORWARDED_ATTACHMENTS_CACHE_DIR)
+ override val useIndependentConnection: Boolean = false
override suspend fun doWork(): Result =
withContext(Dispatchers.IO) {
@@ -71,8 +72,6 @@ class ForwardedAttachmentsDownloaderWorker(context: Context, params: WorkerParam
}
try {
- val roomDatabase = FlowCryptRoomDatabase.getDatabase(applicationContext)
-
if (!attCacheDir.exists()) {
if (!attCacheDir.mkdirs()) {
throw IllegalStateException("Create cache directory " + attCacheDir.name + " filed!")
@@ -85,31 +84,32 @@ class ForwardedAttachmentsDownloaderWorker(context: Context, params: WorkerParam
}
}
- AccountViewModel.getAccountEntityWithDecryptedInfoSuspend(roomDatabase.accountDao()
- .getActiveAccount())?.let { account ->
- val newMsgs = roomDatabase.msgDao().getOutboxMsgsByStatesSuspend(
- account = account.email,
- msgStates = listOf(MessageState.NEW_FORWARDED.value)
- )
-
- if (!CollectionUtils.isEmpty(newMsgs)) {
- if (account.useAPI) {
- when (account.accountType) {
- AccountEntity.ACCOUNT_TYPE_GOOGLE -> {
- downloadForwardedAtts(account)
- }
-
- else -> throw ManualHandledException("Unsupported provider")
- }
- } else {
- OpenStoreHelper.openStore(applicationContext, account, OpenStoreHelper.getAccountSess(applicationContext, account)).use { store ->
- downloadForwardedAtts(account, store)
+ val account = AccountViewModel.getAccountEntityWithDecryptedInfoSuspend(
+ roomDatabase.accountDao().getActiveAccountSuspend())
+ ?: return@withContext Result.success()
+
+ val newMsgs = roomDatabase.msgDao().getOutboxMsgsByStatesSuspend(
+ account = account.email,
+ msgStates = listOf(MessageState.NEW_FORWARDED.value)
+ )
+
+ if (!CollectionUtils.isEmpty(newMsgs)) {
+ if (account.useAPI) {
+ when (account.accountType) {
+ AccountEntity.ACCOUNT_TYPE_GOOGLE -> {
+ downloadForwardedAtts(account)
}
+
+ else -> throw ManualHandledException("Unsupported provider")
+ }
+ } else {
+ OpenStoreHelper.openStore(applicationContext, account, OpenStoreHelper.getAccountSess(applicationContext, account)).use { store ->
+ downloadForwardedAtts(account, store)
}
}
}
- return@withContext Result.success()
+ return@withContext rescheduleIfActiveAccountWasChanged(account)
} catch (e: Exception) {
e.printStackTrace()
ExceptionUtil.handleError(e)
diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/workmanager/MessagesSenderWorker.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/workmanager/MessagesSenderWorker.kt
index e7c93e6f45..16b6ab777a 100644
--- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/workmanager/MessagesSenderWorker.kt
+++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/workmanager/MessagesSenderWorker.kt
@@ -12,7 +12,6 @@ import android.text.TextUtils
import android.util.Base64
import androidx.core.app.NotificationCompat
import androidx.work.Constraints
-import androidx.work.CoroutineWorker
import androidx.work.ExistingWorkPolicy
import androidx.work.ForegroundInfo
import androidx.work.NetworkType
@@ -84,7 +83,9 @@ import javax.net.ssl.SSLException
* Time: 18:43
* E-mail: DenBond7@gmail.com
*/
-class MessagesSenderWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
+class MessagesSenderWorker(context: Context, params: WorkerParameters) : BaseWorker(context, params) {
+ override val useIndependentConnection: Boolean = false
+
override suspend fun doWork(): Result =
withContext(Dispatchers.IO) {
LogsUtil.d(TAG, "doWork")
@@ -92,8 +93,6 @@ class MessagesSenderWorker(context: Context, params: WorkerParameters) : Corouti
return@withContext Result.success()
}
- val roomDatabase = FlowCryptRoomDatabase.getDatabase(applicationContext)
-
try {
val account = AccountViewModel.getAccountEntityWithDecryptedInfoSuspend(
roomDatabase.accountDao().getActiveAccountSuspend())
@@ -136,7 +135,7 @@ class MessagesSenderWorker(context: Context, params: WorkerParameters) : Corouti
}
}
- return@withContext Result.success()
+ return@withContext rescheduleIfActiveAccountWasChanged(account)
} catch (e: Exception) {
e.printStackTrace()
when (e) {
diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/workmanager/sync/BaseSyncWorker.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/workmanager/sync/BaseSyncWorker.kt
index 9576927403..1d8d051f94 100644
--- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/workmanager/sync/BaseSyncWorker.kt
+++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/workmanager/sync/BaseSyncWorker.kt
@@ -12,7 +12,6 @@ import com.flowcrypt.email.R
import com.flowcrypt.email.api.email.IMAPStoreConnection
import com.flowcrypt.email.api.email.IMAPStoreManager
import com.flowcrypt.email.api.email.gmail.GmailApiHelper
-import com.flowcrypt.email.database.FlowCryptRoomDatabase
import com.flowcrypt.email.database.entity.AccountEntity
import com.flowcrypt.email.jetpack.viewmodel.AccountViewModel
import com.flowcrypt.email.jetpack.workmanager.BaseWorker
@@ -31,7 +30,6 @@ import javax.mail.Store
*/
abstract class BaseSyncWorker(context: Context, params: WorkerParameters) : BaseWorker(context, params) {
override val useIndependentConnection: Boolean = false
- protected val roomDatabase = FlowCryptRoomDatabase.getDatabase(applicationContext)
abstract suspend fun runIMAPAction(accountEntity: AccountEntity, store: Store)
abstract suspend fun runAPIAction(accountEntity: AccountEntity)
@@ -75,7 +73,7 @@ abstract class BaseSyncWorker(context: Context, params: WorkerParameters) : Base
}
}
- return@withContext Result.success()
+ return@withContext rescheduleIfActiveAccountWasChanged(activeAccountEntity)
} catch (e: Exception) {
e.printStackTrace()
return@withContext when (e) {
diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/node/NodeSecret.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/node/NodeSecret.kt
index 69d01d3084..c24bdcbaf5 100644
--- a/FlowCrypt/src/main/java/com/flowcrypt/email/node/NodeSecret.kt
+++ b/FlowCrypt/src/main/java/com/flowcrypt/email/node/NodeSecret.kt
@@ -6,18 +6,18 @@
package com.flowcrypt.email.node
import android.util.Base64
-import org.spongycastle.asn1.x500.X500Name
-import org.spongycastle.asn1.x509.Extension
-import org.spongycastle.asn1.x509.KeyUsage
-import org.spongycastle.asn1.x509.SubjectPublicKeyInfo
-import org.spongycastle.cert.X509v3CertificateBuilder
-import org.spongycastle.jce.provider.BouncyCastleProvider
-import org.spongycastle.openssl.PEMKeyPair
-import org.spongycastle.openssl.PEMParser
-import org.spongycastle.openssl.jcajce.JcaMiscPEMGenerator
-import org.spongycastle.openssl.jcajce.JcaPEMKeyConverter
-import org.spongycastle.operator.jcajce.JcaContentSignerBuilder
-import org.spongycastle.util.io.pem.PemWriter
+import org.bouncycastle.asn1.x500.X500Name
+import org.bouncycastle.asn1.x509.Extension
+import org.bouncycastle.asn1.x509.KeyUsage
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
+import org.bouncycastle.cert.X509v3CertificateBuilder
+import org.bouncycastle.jce.provider.BouncyCastleProvider
+import org.bouncycastle.openssl.PEMKeyPair
+import org.bouncycastle.openssl.PEMParser
+import org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator
+import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
+import org.bouncycastle.util.io.pem.PemWriter
import java.io.BufferedReader
import java.io.ByteArrayInputStream
import java.io.IOException
@@ -206,7 +206,7 @@ class NodeSecret @JvmOverloads internal constructor(writablePath: String,
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.addProvider(BouncyCastleProvider()) // takes about 150ms and is not always needed
}
- val cf = CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME)
+ val cf = CertificateFactory.getInstance("X.509")
return cf.generateCertificate(inputStream) as X509Certificate
}
}
diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/service/actionqueue/actions/EncryptPrivateKeysIfNeededAction.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/service/actionqueue/actions/EncryptPrivateKeysIfNeededAction.kt
index 3c56b981cf..e243c3ab3d 100644
--- a/FlowCrypt/src/main/java/com/flowcrypt/email/service/actionqueue/actions/EncryptPrivateKeysIfNeededAction.kt
+++ b/FlowCrypt/src/main/java/com/flowcrypt/email/service/actionqueue/actions/EncryptPrivateKeysIfNeededAction.kt
@@ -38,7 +38,7 @@ data class EncryptPrivateKeysIfNeededAction @JvmOverloads constructor(override v
override val type: Action.Type = Action.Type.ENCRYPT_PRIVATE_KEYS
override fun run(context: Context) {
- val keyEntities = KeysStorageImpl.getInstance(context).getAllPgpPrivateKeys()
+ val keyEntities = KeysStorageImpl.getInstance(context).getAllPgpPrivateKeys().map { it.copy() }
val modifiedKeyEntities = mutableListOf()
val roomDatabase = FlowCryptRoomDatabase.getDatabase(context)
diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/EmailManagerActivity.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/EmailManagerActivity.kt
index 5ff98c9295..ac4bbf3f77 100644
--- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/EmailManagerActivity.kt
+++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/EmailManagerActivity.kt
@@ -44,8 +44,6 @@ import com.flowcrypt.email.api.email.model.LocalFolder
import com.flowcrypt.email.database.FlowCryptRoomDatabase
import com.flowcrypt.email.database.MessageState
import com.flowcrypt.email.database.entity.AccountEntity
-import com.flowcrypt.email.extensions.decrementSafely
-import com.flowcrypt.email.extensions.incrementSafely
import com.flowcrypt.email.extensions.showTwoWayDialogFragment
import com.flowcrypt.email.extensions.toast
import com.flowcrypt.email.jetpack.viewmodel.ActionsViewModel
@@ -88,7 +86,7 @@ import kotlinx.coroutines.launch
* E-mail: DenBond7@gmail.com
*/
class EmailManagerActivity : BaseEmailListActivity(), NavigationView.OnNavigationItemSelectedListener,
- View.OnClickListener, SearchView.OnQueryTextListener, TwoWayDialogFragment.OnTwoWayDialogListener {
+ SearchView.OnQueryTextListener, TwoWayDialogFragment.OnTwoWayDialogListener {
private lateinit var client: GoogleSignInClient
private val labelsViewModel: LabelsViewModel by viewModels()
@@ -284,16 +282,6 @@ class EmailManagerActivity : BaseEmailListActivity(), NavigationView.OnNavigatio
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
- REQUEST_CODE_ADD_NEW_ACCOUNT -> when (resultCode) {
- Activity.RESULT_OK -> {
- countingIdlingResource.incrementSafely()
- finish()
- IdleService.restart(this@EmailManagerActivity)
- runEmailManagerActivity(this@EmailManagerActivity)
- countingIdlingResource.decrementSafely()
- }
- }
-
REQUEST_CODE_SIGN_IN -> when (resultCode) {
Activity.RESULT_OK -> {
val signInResult = Auth.GoogleSignInApi.getSignInResultFromIntent(data)
@@ -349,16 +337,6 @@ class EmailManagerActivity : BaseEmailListActivity(), NavigationView.OnNavigatio
return true
}
- override fun onClick(v: View) {
- when (v.id) {
- R.id.floatActionButtonCompose -> startActivity(CreateMessageActivity.generateIntent(this, null,
- MessageEncryptionType.ENCRYPTED))
-
- R.id.viewIdAddNewAccount -> startActivityForResult(Intent(this, SignInActivity::class.java)
- .apply { action = SignInActivity.ACTION_ADD_ONE_MORE_ACCOUNT }, REQUEST_CODE_ADD_NEW_ACCOUNT)
- }
- }
-
override fun onRetryGoogleAuth() {
GoogleApiClientHelper.signInWithGmailUsingOAuth2(this, client, rootView, REQUEST_CODE_SIGN_IN)
}
@@ -506,7 +484,9 @@ class EmailManagerActivity : BaseEmailListActivity(), NavigationView.OnNavigatio
accountManagementLayout?.visibility = View.GONE
accountManagementLayout?.let { navigationView?.addHeaderView(it) }
- findViewById(R.id.floatActionButtonCompose)?.setOnClickListener(this)
+ findViewById(R.id.floatActionButtonCompose)?.setOnClickListener {
+ startActivity(CreateMessageActivity.generateIntent(this, null, MessageEncryptionType.ENCRYPTED))
+ }
activeAccount?.let { navigationView?.getHeaderView(0)?.let { view -> initUserProfileView(view) } }
}
@@ -528,15 +508,16 @@ class EmailManagerActivity : BaseEmailListActivity(), NavigationView.OnNavigatio
}
textViewUserEmail.text = it.email
- if (it.photoUrl?.isNotEmpty() == true) {
- GlideApp.with(this)
- .load(it.photoUrl)
- .apply(RequestOptions()
- .centerCrop()
- .transform(CircleTransformation())
- .error(R.mipmap.ic_account_default_photo))
- .into(imageViewUserPhoto)
- }
+ val resource = if (it.photoUrl?.isNotEmpty() == true) {
+ it.photoUrl
+ } else R.mipmap.ic_account_default_photo
+ GlideApp.with(this)
+ .load(resource)
+ .apply(RequestOptions()
+ .centerCrop()
+ .transform(CircleTransformation())
+ .error(R.mipmap.ic_account_default_photo))
+ .into(imageViewUserPhoto)
}
currentAccountDetailsItem = view.findViewById(R.id.layoutUserDetails)
@@ -576,7 +557,11 @@ class EmailManagerActivity : BaseEmailListActivity(), NavigationView.OnNavigatio
}
val addNewAccountView = LayoutInflater.from(this).inflate(R.layout.add_account, accountManagementLayout, false)
- addNewAccountView.setOnClickListener(this)
+ addNewAccountView.setOnClickListener {
+ startActivityForResult(Intent(this, SignInActivity::class.java)
+ .apply { action = SignInActivity.ACTION_ADD_ONE_MORE_ACCOUNT }, REQUEST_CODE_ADD_NEW_ACCOUNT)
+ drawerLayout?.closeDrawer(GravityCompat.START)
+ }
accountManagementLayout?.addView(addNewAccountView)
return accountManagementLayout as LinearLayout
@@ -612,9 +597,8 @@ class EmailManagerActivity : BaseEmailListActivity(), NavigationView.OnNavigatio
val roomDatabase = FlowCryptRoomDatabase.getDatabase(this@EmailManagerActivity)
WorkManager.getInstance(applicationContext).cancelAllWorkByTag(BaseSyncWorker.TAG_SYNC)
roomDatabase.accountDao().switchAccountSuspend(account)
- finish()
- IdleService.restart(this@EmailManagerActivity)
- runEmailManagerActivity(this@EmailManagerActivity)
+ currentAccountDetailsItem?.performClick()
+ drawerLayout?.closeDrawer(GravityCompat.START)
}
}
@@ -623,25 +607,25 @@ class EmailManagerActivity : BaseEmailListActivity(), NavigationView.OnNavigatio
private fun setupLabelsViewModel() {
labelsViewModel.foldersManagerLiveData.observe(this, {
+ val differentAccount = foldersManager?.account != it.account
+
this.foldersManager = it
- if (foldersManager?.allFolders?.isNotEmpty() == true) {
- val mailLabels = navigationView?.menu?.findItem(R.id.mailLabels)
- mailLabels?.subMenu?.clear()
+ val mailLabels = navigationView?.menu?.findItem(R.id.mailLabels)
+ mailLabels?.subMenu?.clear()
- foldersManager?.getSortedNames()?.forEach { name ->
- mailLabels?.subMenu?.add(name)
- if (JavaEmailConstants.FOLDER_OUTBOX == name) {
- addOutboxLabel(mailLabels, name)
- }
+ foldersManager?.getSortedNames()?.forEach { name ->
+ mailLabels?.subMenu?.add(name)
+ if (JavaEmailConstants.FOLDER_OUTBOX == name) {
+ addOutboxLabel(mailLabels, name)
}
+ }
- for (localFolder in foldersManager?.customLabels ?: emptyList()) {
- mailLabels?.subMenu?.add(localFolder.folderAlias)
- }
+ for (localFolder in foldersManager?.customLabels ?: emptyList()) {
+ mailLabels?.subMenu?.add(localFolder.folderAlias)
}
- if (currentFolder == null) {
+ if (differentAccount || currentFolder == null) {
currentFolder = foldersManager?.folderInbox
if (currentFolder == null) {
currentFolder = foldersManager?.findInboxFolder()
diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/base/BaseActivity.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/base/BaseActivity.kt
index 76b3a824ac..5e0be33c86 100644
--- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/base/BaseActivity.kt
+++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/base/BaseActivity.kt
@@ -32,7 +32,6 @@ import com.flowcrypt.email.jetpack.viewmodel.RoomBasicViewModel
import com.flowcrypt.email.jetpack.workmanager.sync.BaseSyncWorker
import com.flowcrypt.email.node.Node
import com.flowcrypt.email.service.IdleService
-import com.flowcrypt.email.ui.activity.EmailManagerActivity
import com.flowcrypt.email.ui.activity.SignInActivity
import com.flowcrypt.email.ui.activity.settings.FeedbackActivity
import com.flowcrypt.email.util.GeneralUtil
@@ -234,23 +233,16 @@ abstract class BaseActivity : AppCompatActivity() {
WorkManager.getInstance(applicationContext).cancelAllWorkByTag(BaseSyncWorker.TAG_SYNC)
val roomDatabase = FlowCryptRoomDatabase.getDatabase(applicationContext)
- //remove all info about the given account from the local db
- roomDatabase.accountDao().deleteSuspend(accountEntity)
+ roomDatabase.accountDao().logout(accountEntity)
removeAccountFromAccountManager(accountEntity)
//todo-denbond7 Improve this via onDelete = ForeignKey.CASCADE
+ //remove all info about the given account from the local db
roomDatabase.msgDao().deleteByEmailSuspend(accountEntity.email)
roomDatabase.attachmentDao().deleteByEmailSuspend(accountEntity.email)
- val nonactiveAccounts = roomDatabase.accountDao().getAllNonactiveAccountsSuspend()
- if (nonactiveAccounts.isNotEmpty()) {
- val firstNonactiveAccount = nonactiveAccounts.first()
- roomDatabase.accountDao().updateAccountsSuspend(roomDatabase.accountDao().getAccountsSuspend().map { it.copy(isActive = false) })
- roomDatabase.accountDao().updateAccountSuspend(firstNonactiveAccount.copy(isActive = true))
- finish()
- IdleService.restart(applicationContext)
- EmailManagerActivity.runEmailManagerActivity(this@BaseActivity)
- } else {
+ val newActiveAccount = roomDatabase.accountDao().getActiveAccountSuspend()
+ if (newActiveAccount == null) {
roomDatabase.contactsDao().deleteAll()
stopService(Intent(applicationContext, IdleService::class.java))
val intent = Intent(applicationContext, SignInActivity::class.java)
diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/EmailListFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/EmailListFragment.kt
index 387920cff4..17e950a661 100644
--- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/EmailListFragment.kt
+++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/EmailListFragment.kt
@@ -301,7 +301,7 @@ class EmailListFragment : BaseFragment(), ListProgressBehaviour,
newFolder = it.copy(),
forceClearFolderCache = isForceClearCacheNeeded,
deleteAllMsgs = deleteAllMsgs)
- }
+ } ?: labelsViewModel.loadLabels()
}
/**
diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/preferences/NotificationsSettingsFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/preferences/NotificationsSettingsFragment.kt
index e7b14d7438..6124efa960 100644
--- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/preferences/NotificationsSettingsFragment.kt
+++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/preferences/NotificationsSettingsFragment.kt
@@ -6,7 +6,6 @@
package com.flowcrypt.email.ui.activity.fragment.preferences
import android.content.Intent
-import android.os.Build
import android.os.Bundle
import android.provider.Settings
import androidx.preference.ListPreference
@@ -41,13 +40,10 @@ open class NotificationsSettingsFragment : BasePreferenceFragment(), Preference.
override fun onPreferenceClick(preference: Preference?): Boolean {
return when (preference?.key) {
Constants.PREF_KEY_MANAGE_NOTIFICATIONS -> {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- val intent = Intent()
- intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS
- intent.putExtra(Settings.EXTRA_APP_PACKAGE, BuildConfig.APPLICATION_ID)
- startActivity(intent)
- }
-
+ val intent = Intent()
+ intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS
+ intent.putExtra(Settings.EXTRA_APP_PACKAGE, BuildConfig.APPLICATION_ID)
+ startActivity(intent)
true
}
@@ -95,11 +91,7 @@ open class NotificationsSettingsFragment : BasePreferenceFragment(), Preference.
private fun initPreferences(isEncryptedModeEnabled: Boolean) {
val preferenceSettingsSecurity = findPreference(Constants.PREF_KEY_MANAGE_NOTIFICATIONS)
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- preferenceSettingsSecurity?.onPreferenceClickListener = this
- } else {
- preferenceSettingsSecurity?.isVisible = false
- }
+ preferenceSettingsSecurity?.onPreferenceClickListener = this
val filter = findPreference(Constants.PREF_KEY_MESSAGES_NOTIFICATION_FILTER) as ListPreference
filter.entryValues = levels
diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/notifications/NotificationChannelManager.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/notifications/NotificationChannelManager.kt
index a07b5aebaf..0d72ab1080 100644
--- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/notifications/NotificationChannelManager.kt
+++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/notifications/NotificationChannelManager.kt
@@ -8,13 +8,10 @@ package com.flowcrypt.email.ui.notifications
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
-import android.os.Build
-import androidx.annotation.RequiresApi
import com.flowcrypt.email.R
/**
- * This manager does job of register [NotificationChannel] of the app. The [NotificationChannel] was
- * added in the [Build.VERSION_CODES.O] and doesn't work on previous Android versions.
+ * This manager does job of register [NotificationChannel] of the app.
*
* @author Denis Bondarenko
* Date: 17.10.2017
@@ -34,15 +31,13 @@ object NotificationChannelManager {
* @param context Interface to global information about an application environment.
*/
fun registerNotificationChannels(context: Context) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
-
- notificationManager.createNotificationChannel(genGeneralNotificationChannel(context))
- notificationManager.createNotificationChannel(genAttsNotificationChannel(context))
- notificationManager.createNotificationChannel(genMsgsNotificationChannel(context))
- notificationManager.createNotificationChannel(genErrorNotificationChannel(context))
- notificationManager.createNotificationChannel(genSyncNotificationChannel(context))
- }
+ val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+
+ notificationManager.createNotificationChannel(genGeneralNotificationChannel(context))
+ notificationManager.createNotificationChannel(genAttsNotificationChannel(context))
+ notificationManager.createNotificationChannel(genMsgsNotificationChannel(context))
+ notificationManager.createNotificationChannel(genErrorNotificationChannel(context))
+ notificationManager.createNotificationChannel(genSyncNotificationChannel(context))
}
/**
@@ -51,7 +46,6 @@ object NotificationChannelManager {
* @param context Interface to global information about an application environment.
* @return Generated [NotificationChannel]
*/
- @RequiresApi(api = Build.VERSION_CODES.O)
private fun genMsgsNotificationChannel(context: Context): NotificationChannel {
val name = context.getString(R.string.messages)
val description = context.getString(R.string.messages_notification_channel)
@@ -71,7 +65,6 @@ object NotificationChannelManager {
* @param context Interface to global information about an application environment.
* @return Generated [NotificationChannel]
*/
- @RequiresApi(api = Build.VERSION_CODES.O)
private fun genAttsNotificationChannel(context: Context): NotificationChannel {
val name = context.getString(R.string.attachments)
val description = context.getString(R.string.download_attachments_notification_channel)
@@ -91,7 +84,6 @@ object NotificationChannelManager {
* @param context Interface to global information about an application environment.
* @return Generated [NotificationChannel]
*/
- @RequiresApi(api = Build.VERSION_CODES.O)
private fun genGeneralNotificationChannel(context: Context): NotificationChannel {
val name = context.getString(R.string.system)
val description = context.getString(R.string.system_notifications_notification_chanel)
@@ -111,7 +103,6 @@ object NotificationChannelManager {
* @param context Interface to global information about an application environment.
* @return Generated [NotificationChannel]
*/
- @RequiresApi(api = Build.VERSION_CODES.O)
private fun genErrorNotificationChannel(context: Context): NotificationChannel {
val name = context.getString(R.string.errors_notifications)
val description = context.getString(R.string.errors_notifications_notification_chanel)
@@ -131,7 +122,6 @@ object NotificationChannelManager {
* @param context Interface to global information about an application environment.
* @return Generated [NotificationChannel]
*/
- @RequiresApi(api = Build.VERSION_CODES.O)
private fun genSyncNotificationChannel(context: Context): NotificationChannel {
val name = context.getString(R.string.sync)
val description = context.getString(R.string.sync_notifications_notification_chanel)
diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/util/GeneralUtil.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/util/GeneralUtil.kt
index 8b27ba0698..a5fe4c25b1 100644
--- a/FlowCrypt/src/main/java/com/flowcrypt/email/util/GeneralUtil.kt
+++ b/FlowCrypt/src/main/java/com/flowcrypt/email/util/GeneralUtil.kt
@@ -20,7 +20,6 @@ import android.graphics.drawable.Drawable
import android.net.ConnectivityManager
import android.net.NetworkInfo
import android.net.Uri
-import android.os.Build
import android.provider.OpenableColumns
import android.provider.Settings
import android.text.TextUtils
@@ -345,17 +344,11 @@ class GeneralUtil {
/**
* Returns a language code of the current [Locale].
*/
- @SuppressWarnings("deprecation")
- @Suppress("DEPRECATION")
fun getLocaleLanguageCode(context: Context?): String {
context ?: return "en"
- return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- if (context.resources.configuration.locales.isEmpty) {
- "en"
- } else context.resources.configuration.locales.get(0).language.toLowerCase(Locale.getDefault())
- } else {
- context.resources.configuration.locale.language.toLowerCase(Locale.getDefault())
- }
+ return if (context.resources.configuration.locales.isEmpty) {
+ "en"
+ } else context.resources.configuration.locales.get(0).language.toLowerCase(Locale.getDefault())
}
}
}
diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/util/exception/ExceptionResolver.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/util/exception/ExceptionResolver.kt
index 89255e952f..3923be5ff3 100644
--- a/FlowCrypt/src/main/java/com/flowcrypt/email/util/exception/ExceptionResolver.kt
+++ b/FlowCrypt/src/main/java/com/flowcrypt/email/util/exception/ExceptionResolver.kt
@@ -12,6 +12,7 @@ import com.google.api.client.googleapis.json.GoogleJsonResponseException
import com.sun.mail.iap.ConnectionException
import com.sun.mail.smtp.SMTPSendFailedException
import com.sun.mail.util.MailConnectException
+import kotlinx.coroutines.CancellationException
import java.io.IOException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
@@ -145,6 +146,11 @@ object ExceptionResolver {
}
}
+ if (e is CancellationException) {
+ //we can drop such an error because we always use [CoroutineScope]
+ return false
+ }
+
return true
}
}
diff --git a/FlowCrypt/src/main/res/drawable/ic_launcher_foreground.xml b/FlowCrypt/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000000..7a499d4eed
--- /dev/null
+++ b/FlowCrypt/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/FlowCrypt/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/FlowCrypt/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
deleted file mode 100644
index aaa53a5a3a..0000000000
--- a/FlowCrypt/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/FlowCrypt/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/FlowCrypt/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
deleted file mode 100644
index 9fe4144e32..0000000000
--- a/FlowCrypt/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/FlowCrypt/src/main/res/mipmap-anydpi/ic_launcher.xml b/FlowCrypt/src/main/res/mipmap-anydpi/ic_launcher.xml
new file mode 100644
index 0000000000..ac94b34f54
--- /dev/null
+++ b/FlowCrypt/src/main/res/mipmap-anydpi/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/FlowCrypt/src/main/res/mipmap-hdpi/ic_launcher.png b/FlowCrypt/src/main/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index d47b5cde4d..0000000000
Binary files a/FlowCrypt/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ
diff --git a/FlowCrypt/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/FlowCrypt/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
deleted file mode 100644
index 77530ae83f..0000000000
Binary files a/FlowCrypt/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/FlowCrypt/src/main/res/mipmap-mdpi/ic_launcher.png b/FlowCrypt/src/main/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index 3313a06158..0000000000
Binary files a/FlowCrypt/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ
diff --git a/FlowCrypt/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/FlowCrypt/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
deleted file mode 100644
index 25102dd68d..0000000000
Binary files a/FlowCrypt/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/FlowCrypt/src/main/res/mipmap-xhdpi/ic_launcher.png b/FlowCrypt/src/main/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index 879f4329bd..0000000000
Binary files a/FlowCrypt/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ
diff --git a/FlowCrypt/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/FlowCrypt/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
deleted file mode 100644
index 7c559239ce..0000000000
Binary files a/FlowCrypt/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/FlowCrypt/src/main/res/mipmap-xxhdpi/ic_launcher.png b/FlowCrypt/src/main/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index 742d8d6888..0000000000
Binary files a/FlowCrypt/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ
diff --git a/FlowCrypt/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/FlowCrypt/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
deleted file mode 100644
index d64d71642f..0000000000
Binary files a/FlowCrypt/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/FlowCrypt/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/FlowCrypt/src/main/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index 136d9232de..0000000000
Binary files a/FlowCrypt/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ
diff --git a/FlowCrypt/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/FlowCrypt/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
deleted file mode 100644
index 31d681c329..0000000000
Binary files a/FlowCrypt/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/FlowCrypt/src/main/res/values/ic_launcher_background.xml b/FlowCrypt/src/main/res/values/ic_launcher_background.xml
new file mode 100644
index 0000000000..c5d5899fdf
--- /dev/null
+++ b/FlowCrypt/src/main/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+
+
+ #FFFFFF
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index e666bd5942..7ed7792a96 100644
--- a/build.gradle
+++ b/build.gradle
@@ -10,6 +10,7 @@ buildscript {
repositories {
google()
+ mavenCentral()
jcenter()
maven {
@@ -23,7 +24,7 @@ buildscript {
// in the individual module build.gradle files
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.15.0"
- classpath 'com.akaita.android:easylauncher:1.3.1'
+ classpath "com.project.starter:easylauncher:3.10.1"
}
}
@@ -32,6 +33,7 @@ allprojects {
repositories {
google()
+ mavenCentral()
jcenter()
}
}
diff --git a/ext.gradle b/ext.gradle
index 4b628ce43d..9ff134852f 100644
--- a/ext.gradle
+++ b/ext.gradle
@@ -4,14 +4,14 @@
*/
ext {
- kotlinVersion = '1.4.21'
+ kotlinVersion = '1.4.31'
/*Global*/
appVersionCode = 115
appVersionName = '1.1.5'
buildToolsVersion = '29.0.2'
compileSdkVersion = 29
targetSdkVersion = 29
- minSdkVersion = 24
+ minSdkVersion = 26
javaSourceCompatibility = '1.8'
/*https://developer.android.com/jetpack/androidx/androidx-rn*/
androidxBaseVersion = '1.0.0'
@@ -24,20 +24,22 @@ ext {
/* https://developer.android.com/jetpack/androidx/releases/paging#declaring_dependencies*/
pagingVersion = '2.1.2'
/*https://developer.android.com/jetpack/androidx/releases/navigation*/
- navVersion = '2.3.2'
+ navVersion = '2.3.3'
/* https://mvnrepository.com/artifact/androidx.test.espresso*/
espressoVersion = '3.3.0'
/*https://mvnrepository.com/artifact/com.squareup.retrofit2/retrofit*/
retrofitVersion = '2.9.0'
/*https://mvnrepository.com/artifact/com.squareup.okhttp3/logging-interceptor*/
- okhttpVersion = '4.9.0'
+ okhttpVersion = '4.9.1'
/*https://mvnrepository.com/artifact/com.squareup.okio/okio*/
- okioVersion = '2.9.0'
+ okioVersion = '2.10.0'
/*https://mvnrepository.com/artifact/com.squareup.leakcanary/leakcanary-android*/
- leakcanaryVersion = '2.5'
+ leakcanaryVersion = '2.6'
/*https://mvnrepository.com/artifact/com.sun.mail/android-mail*/
- javaMailVersion = '1.6.5'
+ javaMailVersion = '1.6.6'
/*https://mvnrepository.com/artifact/com.github.bumptech.glide/glide*/
- glideVersion = '4.11.0'
- junitVersion = '4.12'
+ glideVersion = '4.12.0'
+ junitVersion = '4.13'
+ /*https://mvnrepository.com/search?q=org.bouncycastle*/
+ bouncycastleVersion = '1.68'
}
diff --git a/gradle.properties b/gradle.properties
index bbfae271eb..648be224a9 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -22,3 +22,5 @@ org.gradle.caching=true
org.gradle.jvmargs=-Xmx2g
android.useAndroidX=true
android.enableJetifier=true
+#https://issuetracker.google.com/issues/159151549#comment7
+android.jetifier.blacklist=bcprov-jdk15on-1.68.jar
diff --git a/script/ci-install-android-sdk.sh b/script/ci-install-android-sdk.sh
index 9fbfb7886c..6dc3252d9c 100755
--- a/script/ci-install-android-sdk.sh
+++ b/script/ci-install-android-sdk.sh
@@ -26,16 +26,17 @@ else
unzip -qq $SDK_ARCHIVE -d $ANDROID_SDK_ROOT
rm $SDK_ARCHIVE
- (echo "yes" | sdkmanager --licenses > /dev/null | grep -v = || true)
- ( sleep 5; echo "y" ) | (sdkmanager "build-tools;29.0.2" "platforms;android-29" > /dev/null | grep -v = || true)
- (sdkmanager "extras;google;m2repository" | grep -v = || true)
- (sdkmanager "platform-tools" | grep -v = || true)
- (sdkmanager "emulator" | grep -v = || true)
- (sdkmanager "ndk;22.0.7026061" | grep -v = || true)
- (sdkmanager "cmake;3.10.2.4988404" | grep -v = || true)
- (sdkmanager "system-images;android-30;google_apis;x86_64" | grep -v = || true)
+ (echo "yes" | ${ANDROID_SDK_ROOT}/tools/bin/sdkmanager --licenses > /dev/null | grep -v = || true)
+ ( sleep 5; echo "y" ) | (${ANDROID_SDK_ROOT}/tools/bin/sdkmanager "build-tools;29.0.2" "platforms;android-29" > /dev/null | grep -v = || true)
+ (${ANDROID_SDK_ROOT}/tools/bin/sdkmanager "extras;google;m2repository" | grep -v = || true)
+ (${ANDROID_SDK_ROOT}/tools/bin/sdkmanager "cmdline-tools;latest" | grep -v = || true)
+ (${ANDROID_SDK_ROOT}/tools/bin/sdkmanager "platform-tools" | grep -v = || true)
+ (${ANDROID_SDK_ROOT}/tools/bin/sdkmanager "emulator" | grep -v = || true)
+ (${ANDROID_SDK_ROOT}/tools/bin/sdkmanager "ndk;22.0.7026061" | grep -v = || true)
+ (${ANDROID_SDK_ROOT}/tools/bin/sdkmanager "cmake;3.10.2.4988404" | grep -v = || true)
+ (${ANDROID_SDK_ROOT}/tools/bin/sdkmanager "system-images;android-30;google_apis;x86" | grep -v = || true)
fi
#Uncomment this for debug
-#~/Android/Sdk/tools/bin/sdkmanager --list
+#~/Android/Sdk/tools/bin/${ANDROID_SDK_ROOT}/tools/bin/sdkmanager --list
diff --git a/script/ci-setup-and-run-emulator.sh b/script/ci-setup-and-run-emulator.sh
index 8e4df38a0b..4360da0f1e 100755
--- a/script/ci-setup-and-run-emulator.sh
+++ b/script/ci-setup-and-run-emulator.sh
@@ -1,10 +1,10 @@
#!/bin/bash
"$ANDROID_SDK_ROOT/emulator/emulator" -accel-check
-echo -ne '\n' | avdmanager -v create avd --name ci-test-pixel-x86-64-api30 --package "system-images;android-30;google_apis;x86_64" --device 'pixel_xl' --abi 'google_apis/x86_64'
-cat ~/.android/avd/ci-test-pixel-x86-64-api30.avd/config.ini
-echo "vm.heapSize=256" >> ~/.android/avd/ci-test-pixel-x86-64-api30.avd/config.ini
-echo "hw.ramSize=3064" >> ~/.android/avd/ci-test-pixel-x86-64-api30.avd/config.ini
-cat ~/.android/avd/ci-test-pixel-x86-64-api30.avd/config.ini
+echo -ne '\n' | avdmanager -v create avd --name ci-test-pixel-x86-api30 --package "system-images;android-30;google_apis;x86" --device 'pixel_xl' --abi 'google_apis/x86'
+cat ~/.android/avd/ci-test-pixel-x86-api30.avd/config.ini
+echo "vm.heapSize=256" >> ~/.android/avd/ci-test-pixel-x86-api30.avd/config.ini
+echo "hw.ramSize=3064" >> ~/.android/avd/ci-test-pixel-x86-api30.avd/config.ini
+cat ~/.android/avd/ci-test-pixel-x86-api30.avd/config.ini
"$ANDROID_SDK_ROOT/emulator/emulator" -list-avds #debug
-"$ANDROID_SDK_ROOT/emulator/emulator" -avd ci-test-pixel-x86-64-api30 -no-window -no-boot-anim -no-audio &
\ No newline at end of file
+"$ANDROID_SDK_ROOT/emulator/emulator" -avd ci-test-pixel-x86-api30 -no-window -no-boot-anim -no-audio &
\ No newline at end of file