Merge branch 'feature/add-to-fdroid-main-repo' into develop
@ -30,9 +30,9 @@ ext {
|
|||||||
javaxAnnotationVersion = '1.0'
|
javaxAnnotationVersion = '1.0'
|
||||||
|
|
||||||
// support lib
|
// support lib
|
||||||
androidSupportAnnotationsVersion = '1.2.0'
|
androidSupportAnnotationsVersion = '1.3.0'
|
||||||
androidSupportAppcompatVersion = '1.3.1'
|
androidSupportAppcompatVersion = '1.4.1'
|
||||||
androidSupportDesignVersion = '1.4.0'
|
androidMaterialDesignVersion = '1.6.0'
|
||||||
|
|
||||||
coreDesugaringVersion = '1.1.5'
|
coreDesugaringVersion = '1.1.5'
|
||||||
|
|
||||||
@ -102,15 +102,16 @@ ext {
|
|||||||
uiautomatorVersion = '2.2.0'
|
uiautomatorVersion = '2.2.0'
|
||||||
androidxTestJunitKtlnVersion = '1.1.3'
|
androidxTestJunitKtlnVersion = '1.1.3'
|
||||||
|
|
||||||
androidxCoreVersion = '1.6.0'
|
androidxCoreVersion = '1.7.0'
|
||||||
androidxFragmentVersion = '1.3.6'
|
androidxFragmentVersion = '1.4.1'
|
||||||
androidxViewpagerVersion = '1.0.0'
|
androidxViewpagerVersion = '1.0.0'
|
||||||
androidxSwiperefreshVersion = '1.1.0'
|
androidxSwiperefreshVersion = '1.1.0'
|
||||||
androidxPreferenceVersion = '1.1.1'
|
androidxPreferenceVersion = '1.2.0'
|
||||||
androidxRecyclerViewVersion = '1.2.1'
|
androidxRecyclerViewVersion = '1.2.1'
|
||||||
androidxDocumentfileVersion = '1.0.1'
|
androidxDocumentfileVersion = '1.0.1'
|
||||||
androidxBiometricVersion = '1.1.0'
|
androidxBiometricVersion = '1.1.0'
|
||||||
androidxTestCoreVersion = '1.4.0'
|
androidxTestCoreVersion = '1.4.0'
|
||||||
|
androidxSplashscreenVersion = '1.0.0-rc01'
|
||||||
|
|
||||||
jsonWebTokenApiVersion = '0.11.5'
|
jsonWebTokenApiVersion = '0.11.5'
|
||||||
|
|
||||||
@ -126,13 +127,14 @@ ext {
|
|||||||
androidxPreference : "androidx.preference:preference:${androidxPreferenceVersion}",
|
androidxPreference : "androidx.preference:preference:${androidxPreferenceVersion}",
|
||||||
documentFile : "androidx.documentfile:documentfile:${androidxDocumentfileVersion}",
|
documentFile : "androidx.documentfile:documentfile:${androidxDocumentfileVersion}",
|
||||||
recyclerView : "androidx.recyclerview:recyclerview:${androidxRecyclerViewVersion}",
|
recyclerView : "androidx.recyclerview:recyclerview:${androidxRecyclerViewVersion}",
|
||||||
|
androidxSplashscreen : "androidx.core:core-splashscreen:${androidxSplashscreenVersion}",
|
||||||
androidxTestCore : "androidx.test:core:${androidxTestCoreVersion}",
|
androidxTestCore : "androidx.test:core:${androidxTestCoreVersion}",
|
||||||
androidxTestJunitKtln : "androidx.test.ext:junit-ktx:${androidxTestJunitKtlnVersion}",
|
androidxTestJunitKtln : "androidx.test.ext:junit-ktx:${androidxTestJunitKtlnVersion}",
|
||||||
commonsCodec : "commons-codec:commons-codec:${commonsCodecVersion}",
|
commonsCodec : "commons-codec:commons-codec:${commonsCodecVersion}",
|
||||||
cryptolib : "org.cryptomator:cryptolib:${cryptolibVersion}",
|
cryptolib : "org.cryptomator:cryptolib:${cryptolibVersion}",
|
||||||
dagger : "com.google.dagger:dagger:${daggerVersion}",
|
dagger : "com.google.dagger:dagger:${daggerVersion}",
|
||||||
daggerCompiler : "com.google.dagger:dagger-compiler:${daggerVersion}",
|
daggerCompiler : "com.google.dagger:dagger-compiler:${daggerVersion}",
|
||||||
design : "com.google.android.material:material:${androidSupportDesignVersion}",
|
design : "com.google.android.material:material:${androidMaterialDesignVersion}",
|
||||||
coreDesugaring : "com.android.tools:desugar_jdk_libs:${coreDesugaringVersion}",
|
coreDesugaring : "com.android.tools:desugar_jdk_libs:${coreDesugaringVersion}",
|
||||||
dropbox : "com.dropbox.core:dropbox-core-sdk:${dropboxVersion}",
|
dropbox : "com.dropbox.core:dropbox-core-sdk:${dropboxVersion}",
|
||||||
espresso : "androidx.test.espresso:espresso-core:${espressoVersion}",
|
espresso : "androidx.test.espresso:espresso-core:${espressoVersion}",
|
||||||
|
@ -53,19 +53,27 @@ android {
|
|||||||
fdroid {
|
fdroid {
|
||||||
dimension "version"
|
dimension "version"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lite {
|
||||||
|
dimension "version"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
playstore {
|
playstore {
|
||||||
java.srcDirs = ['src/main/java', 'src/main/java/', 'src/notFoss/java', 'src/notFoss/java/']
|
java.srcDirs = ['src/main/java/', 'src/apiKey/java/', 'src/apkStorePlaystore/java/']
|
||||||
}
|
}
|
||||||
|
|
||||||
apkstore {
|
apkstore {
|
||||||
java.srcDirs = ['src/main/java', 'src/main/java/', 'src/notFoss/java', 'src/notFoss/java/']
|
java.srcDirs = ['src/main/java/', 'src/apiKey/java/', 'src/apkStorePlaystore/java/']
|
||||||
}
|
}
|
||||||
|
|
||||||
fdroid {
|
fdroid {
|
||||||
java.srcDirs = ['src/main/java', 'src/main/java/', 'src/foss/java', 'src/foss/java/']
|
java.srcDirs = ['src/main/java/', 'src/apiKey/java/', 'src/fdroid/java/']
|
||||||
|
}
|
||||||
|
|
||||||
|
lite {
|
||||||
|
java.srcDirs = ['src/main/java/', 'src/lite/java/']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
packagingOptions {
|
packagingOptions {
|
||||||
@ -95,7 +103,9 @@ dependencies {
|
|||||||
|
|
||||||
implementation project(':domain')
|
implementation project(':domain')
|
||||||
implementation project(':util')
|
implementation project(':util')
|
||||||
implementation project(':pcloud-sdk-java')
|
playstoreImplementation project(':pcloud-sdk-java')
|
||||||
|
apkstoreImplementation project(':pcloud-sdk-java')
|
||||||
|
fdroidImplementation project(':pcloud-sdk-java')
|
||||||
|
|
||||||
coreLibraryDesugaring dependencies.coreDesugaring
|
coreLibraryDesugaring dependencies.coreDesugaring
|
||||||
|
|
||||||
@ -113,9 +123,16 @@ dependencies {
|
|||||||
implementation dependencies.jsonWebTokenJson
|
implementation dependencies.jsonWebTokenJson
|
||||||
|
|
||||||
// cloud
|
// cloud
|
||||||
implementation dependencies.dropbox
|
playstoreImplementation dependencies.dropbox
|
||||||
implementation dependencies.msgraphAuth
|
apkstoreImplementation dependencies.dropbox
|
||||||
implementation dependencies.msgraph
|
fdroidImplementation dependencies.dropbox
|
||||||
|
|
||||||
|
playstoreImplementation dependencies.msgraphAuth
|
||||||
|
apkstoreImplementation dependencies.msgraphAuth
|
||||||
|
fdroidImplementation dependencies.msgraphAuth
|
||||||
|
playstoreImplementation dependencies.msgraph
|
||||||
|
apkstoreImplementation dependencies.msgraph
|
||||||
|
fdroidImplementation dependencies.msgraph
|
||||||
|
|
||||||
implementation dependencies.stax
|
implementation dependencies.stax
|
||||||
api dependencies.minIo
|
api dependencies.minIo
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package org.cryptomator.data.cloud;
|
package org.cryptomator.data.cloud;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
|
||||||
import org.cryptomator.data.cloud.crypto.CryptoCloudContentRepositoryFactory;
|
import org.cryptomator.data.cloud.crypto.CryptoCloudContentRepositoryFactory;
|
||||||
import org.cryptomator.data.cloud.dropbox.DropboxCloudContentRepositoryFactory;
|
import org.cryptomator.data.cloud.dropbox.DropboxCloudContentRepositoryFactory;
|
||||||
import org.cryptomator.data.cloud.googledrive.GoogleDriveCloudContentRepositoryFactory;
|
import org.cryptomator.data.cloud.googledrive.GoogleDriveCloudContentRepositoryFactory;
|
||||||
@ -16,8 +18,6 @@ import java.util.Iterator;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
import static java.util.Arrays.asList;
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class CloudContentRepositoryFactories implements Iterable<CloudContentRepositoryFactory> {
|
public class CloudContentRepositoryFactories implements Iterable<CloudContentRepositoryFactory> {
|
||||||
|
|
@ -0,0 +1,40 @@
|
|||||||
|
package org.cryptomator.data.cloud;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
|
||||||
|
import org.cryptomator.data.cloud.crypto.CryptoCloudContentRepositoryFactory;
|
||||||
|
import org.cryptomator.data.cloud.local.LocalStorageContentRepositoryFactory;
|
||||||
|
import org.cryptomator.data.cloud.s3.S3CloudContentRepositoryFactory;
|
||||||
|
import org.cryptomator.data.cloud.webdav.WebDavCloudContentRepositoryFactory;
|
||||||
|
import org.cryptomator.data.repository.CloudContentRepositoryFactory;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class CloudContentRepositoryFactories implements Iterable<CloudContentRepositoryFactory> {
|
||||||
|
|
||||||
|
private final Iterable<CloudContentRepositoryFactory> factories;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public CloudContentRepositoryFactories(
|
||||||
|
S3CloudContentRepositoryFactory s3Factory, //
|
||||||
|
CryptoCloudContentRepositoryFactory cryptoFactory, //
|
||||||
|
LocalStorageContentRepositoryFactory localStorageFactory, //
|
||||||
|
WebDavCloudContentRepositoryFactory webDavFactory) {
|
||||||
|
|
||||||
|
factories = asList(s3Factory, //
|
||||||
|
cryptoFactory, //
|
||||||
|
localStorageFactory, //
|
||||||
|
webDavFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Iterator<CloudContentRepositoryFactory> iterator() {
|
||||||
|
return factories.iterator();
|
||||||
|
}
|
||||||
|
}
|
@ -29,6 +29,7 @@ platform :android do |options|
|
|||||||
deployToPlaystore(alpha:options[:alpha], beta:options[:beta])
|
deployToPlaystore(alpha:options[:alpha], beta:options[:beta])
|
||||||
deployToServer(alpha:options[:alpha], beta:options[:beta])
|
deployToServer(alpha:options[:alpha], beta:options[:beta])
|
||||||
deployToFDroid(alpha:options[:alpha], beta:options[:beta])
|
deployToFDroid(alpha:options[:alpha], beta:options[:beta])
|
||||||
|
testLite(alpha:options[:alpha], beta:options[:beta])
|
||||||
createGitHubDraftRelease(alpha:options[:alpha], beta:options[:beta])
|
createGitHubDraftRelease(alpha:options[:alpha], beta:options[:beta])
|
||||||
|
|
||||||
slack(
|
slack(
|
||||||
@ -220,6 +221,29 @@ platform :android do |options|
|
|||||||
FileUtils.cp(lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH], "release/Cryptomator-#{version}_fdroid_signed.apk")
|
FileUtils.cp(lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH], "release/Cryptomator-#{version}_fdroid_signed.apk")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
desc "Deploy new lite version"
|
||||||
|
lane :deployLite do |options|
|
||||||
|
gradle(task: "clean")
|
||||||
|
|
||||||
|
gradle(
|
||||||
|
task: "assemble",
|
||||||
|
build_type: "Release",
|
||||||
|
flavor: "lite",
|
||||||
|
print_command: false,
|
||||||
|
properties: {
|
||||||
|
"android.injected.signing.store.file" => ENV["SIGNING_KEYSTORE_PATH"],
|
||||||
|
"android.injected.signing.store.password" => ENV["SIGNING_KEYSTORE_PASSWORD"],
|
||||||
|
"android.injected.signing.key.alias" => ENV["SIGNING_KEY_ALIAS"],
|
||||||
|
"android.injected.signing.key.password" => ENV["SIGNING_KEY_PASSWORD"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
checkTrackingAddedInDependencyUsingIzzyScript(alpha:options[:alpha], beta:options[:beta], flavor: 'lite')
|
||||||
|
checkTrackingAddedInDependencyUsingExodus(alpha:options[:alpha], beta:options[:beta], flavor: 'lite')
|
||||||
|
|
||||||
|
FileUtils.cp(lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH], "release/Cryptomator-#{version}_lite_signed.apk")
|
||||||
|
end
|
||||||
|
|
||||||
desc "Check if tracking added in some dependency using Izzy's script"
|
desc "Check if tracking added in some dependency using Izzy's script"
|
||||||
lane :checkTrackingAddedInDependencyUsingIzzyScript do |options|
|
lane :checkTrackingAddedInDependencyUsingIzzyScript do |options|
|
||||||
flavor = options[:flavor]
|
flavor = options[:flavor]
|
||||||
@ -289,8 +313,9 @@ platform :android do |options|
|
|||||||
|
|
||||||
website_apk_sha256 = Digest::SHA256.hexdigest File.read "release/Cryptomator-#{version}_signed.apk"
|
website_apk_sha256 = Digest::SHA256.hexdigest File.read "release/Cryptomator-#{version}_signed.apk"
|
||||||
fdroid_apk_sha256 = Digest::SHA256.hexdigest File.read "release/Cryptomator-#{version}_fdroid_signed.apk"
|
fdroid_apk_sha256 = Digest::SHA256.hexdigest File.read "release/Cryptomator-#{version}_fdroid_signed.apk"
|
||||||
|
lite_sha256 = Digest::SHA256.hexdigest File.read "release/Cryptomator-#{version}_lite_signed.apk"
|
||||||
|
|
||||||
release_note = "## What's New\n\n" + File.read(release_note_path_en) + "\n\n---\n\nSHA256 Signature: `#{website_apk_sha256}`\nSHA256 Signature fdroid: `#{fdroid_apk_sha256}`\n"
|
release_note = "## What's New\n\n" + File.read(release_note_path_en) + "\n\n---\n\nSHA256 Signature: `#{website_apk_sha256}`\nSHA256 Signature fdroid: `#{fdroid_apk_sha256}`\nSHA256 Signature lite: `#{lite_sha256}`\n"
|
||||||
|
|
||||||
puts release_note
|
puts release_note
|
||||||
|
|
||||||
@ -303,7 +328,7 @@ platform :android do |options|
|
|||||||
commitish: target_branch,
|
commitish: target_branch,
|
||||||
is_draft: true,
|
is_draft: true,
|
||||||
is_prerelease: prerelease,
|
is_prerelease: prerelease,
|
||||||
upload_assets: ["fastlane/release/Cryptomator-#{version}_fdroid_signed.apk", "fastlane/release/Cryptomator-#{version}_signed.apk"]
|
upload_assets: ["fastlane/release/Cryptomator-#{version}_fdroid_signed.apk", "fastlane/release/Cryptomator-#{version}_lite_signed.apk", "fastlane/release/Cryptomator-#{version}_signed.apk"]
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -362,5 +387,23 @@ platform :android do |options|
|
|||||||
|
|
||||||
checkTrackingAddedInDependencyUsingIzzyScript(alpha:options[:alpha], beta:options[:beta], flavor: 'fdroid')
|
checkTrackingAddedInDependencyUsingIzzyScript(alpha:options[:alpha], beta:options[:beta], flavor: 'fdroid')
|
||||||
checkTrackingAddedInDependencyUsingExodus(alpha:options[:alpha], beta:options[:beta], flavor: 'fdroid')
|
checkTrackingAddedInDependencyUsingExodus(alpha:options[:alpha], beta:options[:beta], flavor: 'fdroid')
|
||||||
|
|
||||||
|
gradle(task: "clean")
|
||||||
|
|
||||||
|
gradle(
|
||||||
|
task: "assemble",
|
||||||
|
build_type: "Release",
|
||||||
|
flavor: "lite",
|
||||||
|
print_command: false,
|
||||||
|
properties: {
|
||||||
|
"android.injected.signing.store.file" => ENV["SIGNING_KEYSTORE_PATH"],
|
||||||
|
"android.injected.signing.store.password" => ENV["SIGNING_KEYSTORE_PASSWORD"],
|
||||||
|
"android.injected.signing.key.alias" => ENV["SIGNING_KEY_ALIAS"],
|
||||||
|
"android.injected.signing.key.password" => ENV["SIGNING_KEY_PASSWORD"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
checkTrackingAddedInDependencyUsingIzzyScript(alpha:options[:alpha], beta:options[:beta], flavor: 'lite')
|
||||||
|
checkTrackingAddedInDependencyUsingExodus(alpha:options[:alpha], beta:options[:beta], flavor: 'lite')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -55,6 +55,14 @@ Deploy new version to server
|
|||||||
|
|
||||||
Deploy new version to F-Droid
|
Deploy new version to F-Droid
|
||||||
|
|
||||||
|
### android deployLite
|
||||||
|
|
||||||
|
```sh
|
||||||
|
[bundle exec] fastlane android deployLite
|
||||||
|
```
|
||||||
|
|
||||||
|
Deploy new lite version
|
||||||
|
|
||||||
### android checkTrackingAddedInDependencyUsingIzzyScript
|
### android checkTrackingAddedInDependencyUsingIzzyScript
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
1
fastlane/izzyscript/result_lite.json
Normal file
@ -50,8 +50,6 @@ android {
|
|||||||
buildConfigField "String", "PCLOUD_CLIENT_ID", "\"" + getApiKey('PCLOUD_CLIENT_ID') + "\""
|
buildConfigField "String", "PCLOUD_CLIENT_ID", "\"" + getApiKey('PCLOUD_CLIENT_ID') + "\""
|
||||||
|
|
||||||
manifestPlaceholders = [DROPBOX_API_KEY: getApiKey('DROPBOX_API_KEY'), ONEDRIVE_API_KEY_DECODED: getOnedriveApiKey()]
|
manifestPlaceholders = [DROPBOX_API_KEY: getApiKey('DROPBOX_API_KEY'), ONEDRIVE_API_KEY_DECODED: getOnedriveApiKey()]
|
||||||
|
|
||||||
resValue "string", "app_id", androidApplicationId
|
|
||||||
}
|
}
|
||||||
|
|
||||||
debug {
|
debug {
|
||||||
@ -69,8 +67,6 @@ android {
|
|||||||
|
|
||||||
applicationIdSuffix ".debug"
|
applicationIdSuffix ".debug"
|
||||||
versionNameSuffix '-DEBUG'
|
versionNameSuffix '-DEBUG'
|
||||||
|
|
||||||
resValue "string", "app_id", androidApplicationId + applicationIdSuffix
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,19 +84,30 @@ android {
|
|||||||
fdroid {
|
fdroid {
|
||||||
dimension "version"
|
dimension "version"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lite {
|
||||||
|
dimension "version"
|
||||||
|
|
||||||
|
applicationIdSuffix ".lite"
|
||||||
|
resValue "string", "app_id", androidApplicationId + applicationIdSuffix
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
playstore {
|
playstore {
|
||||||
java.srcDirs = ['src/main/java', 'src/main/java/', 'src/notFoss/java', 'src/notFoss/java/']
|
java.srcDirs = ['src/main/java', 'src/apiKey/java/', 'src/apkStorePlaystore/java/']
|
||||||
}
|
}
|
||||||
|
|
||||||
apkstore {
|
apkstore {
|
||||||
java.srcDirs = ['src/main/java', 'src/main/java/', 'src/notFoss/java', 'src/notFoss/java/']
|
java.srcDirs = ['src/main/java/', 'src/apiKey/java/', 'src/apkStorePlaystore/java/']
|
||||||
}
|
}
|
||||||
|
|
||||||
fdroid {
|
fdroid {
|
||||||
java.srcDirs = ['src/main/java', 'src/main/java/', 'src/foss/java', 'src/foss/java/']
|
java.srcDirs = ['src/main/java/', 'src/apiKey/java/', 'src/fdroid/java/', 'src/fdroidAndLite/java/']
|
||||||
|
}
|
||||||
|
|
||||||
|
lite {
|
||||||
|
java.srcDirs = ['src/main/java/', 'src/lite/java/', 'src/fdroidAndLite/java/']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
packagingOptions {
|
packagingOptions {
|
||||||
@ -142,14 +149,22 @@ dependencies {
|
|||||||
implementation dependencies.androidxCore
|
implementation dependencies.androidxCore
|
||||||
implementation dependencies.androidxFragment
|
implementation dependencies.androidxFragment
|
||||||
implementation dependencies.androidxViewpager
|
implementation dependencies.androidxViewpager
|
||||||
|
implementation dependencies.androidxSplashscreen
|
||||||
implementation dependencies.androidxSwiperefresh
|
implementation dependencies.androidxSwiperefresh
|
||||||
implementation dependencies.androidxPreference
|
implementation dependencies.androidxPreference
|
||||||
implementation dependencies.androidxBiometric
|
implementation dependencies.androidxBiometric
|
||||||
|
|
||||||
// cloud
|
// cloud
|
||||||
implementation dependencies.dropbox
|
playstoreImplementation dependencies.dropbox
|
||||||
implementation dependencies.msgraph
|
apkstoreImplementation dependencies.dropbox
|
||||||
implementation dependencies.msgraphAuth
|
fdroidImplementation dependencies.dropbox
|
||||||
|
|
||||||
|
playstoreImplementation dependencies.msgraphAuth
|
||||||
|
apkstoreImplementation dependencies.msgraphAuth
|
||||||
|
fdroidImplementation dependencies.msgraphAuth
|
||||||
|
playstoreImplementation dependencies.msgraph
|
||||||
|
apkstoreImplementation dependencies.msgraph
|
||||||
|
fdroidImplementation dependencies.msgraph
|
||||||
|
|
||||||
playstoreImplementation(dependencies.googleApiServicesDrive) {
|
playstoreImplementation(dependencies.googleApiServicesDrive) {
|
||||||
exclude module: 'guava-jdk5'
|
exclude module: 'guava-jdk5'
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
package org.cryptomator.presentation.presenter
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.dropbox.core.android.Auth
|
||||||
|
import org.cryptomator.presentation.BuildConfig
|
||||||
|
|
||||||
|
object DropboxAuthHelper {
|
||||||
|
|
||||||
|
fun startOAuth2Authentication(context: Context) {
|
||||||
|
Auth.startOAuth2Authentication(context, BuildConfig.DROPBOX_API_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getOAuth2Token(): String? {
|
||||||
|
return Auth.getOAuth2Token()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,139 @@
|
|||||||
|
package org.cryptomator.presentation.presenter
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import com.microsoft.identity.client.AuthenticationCallback
|
||||||
|
import com.microsoft.identity.client.IAccount
|
||||||
|
import com.microsoft.identity.client.IAuthenticationResult
|
||||||
|
import com.microsoft.identity.client.IMultipleAccountPublicClientApplication
|
||||||
|
import com.microsoft.identity.client.IPublicClientApplication
|
||||||
|
import com.microsoft.identity.client.PublicClientApplication
|
||||||
|
import com.microsoft.identity.client.exception.MsalException
|
||||||
|
import com.microsoft.identity.client.exception.MsalUiRequiredException
|
||||||
|
import org.cryptomator.domain.OnedriveCloud
|
||||||
|
import org.cryptomator.domain.exception.FatalBackendException
|
||||||
|
import org.cryptomator.presentation.R
|
||||||
|
import org.cryptomator.util.crypto.CredentialCryptor
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
object OnedriveAuthentication {
|
||||||
|
|
||||||
|
fun refreshOrCheckAuth(activity: Activity, cloud: OnedriveCloud, success: (cloud: OnedriveCloud) -> Unit, failed: (e: FatalBackendException) -> Unit) {
|
||||||
|
PublicClientApplication.createMultipleAccountPublicClientApplication(
|
||||||
|
activity.applicationContext,
|
||||||
|
R.raw.auth_config_onedrive,
|
||||||
|
object : IPublicClientApplication.IMultipleAccountApplicationCreatedListener {
|
||||||
|
override fun onCreated(application: IMultipleAccountPublicClientApplication) {
|
||||||
|
application.getAccounts(object : IPublicClientApplication.LoadAccountsCallback {
|
||||||
|
override fun onTaskCompleted(accounts: List<IAccount>) {
|
||||||
|
if (accounts.isEmpty()) {
|
||||||
|
application.acquireToken(activity, AuthenticateCloudPresenter.onedriveScopes(), getAuthInteractiveCallback(activity.applicationContext, cloud, success, failed))
|
||||||
|
} else {
|
||||||
|
accounts.find { account -> account.username == cloud.username() }?.let {
|
||||||
|
application.acquireTokenSilentAsync(
|
||||||
|
AuthenticateCloudPresenter.onedriveScopes(),
|
||||||
|
it,
|
||||||
|
"https://login.microsoftonline.com/common",
|
||||||
|
getAuthSilentCallback(activity, cloud, success, failed, application)
|
||||||
|
)
|
||||||
|
} ?: application.acquireToken(activity, AuthenticateCloudPresenter.onedriveScopes(), getAuthInteractiveCallback(activity.applicationContext, cloud, success, failed))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(e: MsalException) {
|
||||||
|
Timber.tag("AuthenticateCloudPresenter").e(e, "Error to get accounts")
|
||||||
|
failed(FatalBackendException(e))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(e: MsalException) {
|
||||||
|
Timber.tag("AuthenticateCloudPresenter").i(e, "Error in configuration")
|
||||||
|
failed(FatalBackendException(e))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAuthSilentCallback(
|
||||||
|
activity: Activity,
|
||||||
|
cloud: OnedriveCloud,
|
||||||
|
success: (cloud: OnedriveCloud) -> Unit,
|
||||||
|
failed: (e: FatalBackendException) -> Unit,
|
||||||
|
application: IMultipleAccountPublicClientApplication
|
||||||
|
): AuthenticationCallback {
|
||||||
|
return object : AuthenticationCallback {
|
||||||
|
|
||||||
|
override fun onSuccess(authenticationResult: IAuthenticationResult) {
|
||||||
|
onTokenObtained(activity.applicationContext, cloud, authenticationResult, success)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(e: MsalException) {
|
||||||
|
Timber.tag("AuthenticateCloudPresenter").e(e, "Failed to acquireToken")
|
||||||
|
when (e) {
|
||||||
|
is MsalUiRequiredException -> {
|
||||||
|
/* Tokens expired or no session, retry with interactive */
|
||||||
|
application.acquireToken(activity, AuthenticateCloudPresenter.onedriveScopes(), getAuthInteractiveCallback(activity.applicationContext, cloud, success, failed))
|
||||||
|
}
|
||||||
|
else -> failed(FatalBackendException(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCancel() {
|
||||||
|
Timber.tag("AuthenticateCloudPresenter").i("User cancelled login")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onTokenObtained(context: Context, cloud: OnedriveCloud?, authenticationResult: IAuthenticationResult, success: (cloud: OnedriveCloud) -> Unit) {
|
||||||
|
Timber.tag("AuthenticateCloudPresenter").i("Successfully authenticated")
|
||||||
|
val accessToken = CredentialCryptor.getInstance(context).encrypt(authenticationResult.accessToken)
|
||||||
|
val cloudBuilder = cloud?.let { OnedriveCloud.aCopyOf(it) } ?: OnedriveCloud.aOnedriveCloud()
|
||||||
|
val onedriveSkeleton = cloudBuilder.withAccessToken(accessToken).withUsername(authenticationResult.account.username).build()
|
||||||
|
success(onedriveSkeleton)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAuthenticatedOnedriveCloud(activity: Activity, success: (cloud: OnedriveCloud) -> Unit, failed: (e: FatalBackendException) -> Unit) {
|
||||||
|
PublicClientApplication.createMultipleAccountPublicClientApplication(
|
||||||
|
activity.applicationContext,
|
||||||
|
R.raw.auth_config_onedrive,
|
||||||
|
object : IPublicClientApplication.IMultipleAccountApplicationCreatedListener {
|
||||||
|
override fun onCreated(application: IMultipleAccountPublicClientApplication) {
|
||||||
|
application.getAccounts(object : IPublicClientApplication.LoadAccountsCallback {
|
||||||
|
override fun onTaskCompleted(accounts: List<IAccount>) {
|
||||||
|
application.acquireToken(activity, AuthenticateCloudPresenter.onedriveScopes(), getAuthInteractiveCallback(activity.applicationContext, null, success, failed))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(e: MsalException) {
|
||||||
|
Timber.tag("AuthenticateCloudPresenter").e(e, "Error to get accounts")
|
||||||
|
failed(FatalBackendException(e))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(e: MsalException) {
|
||||||
|
Timber.tag("AuthenticateCloudPresenter").i(e, "Error in configuration")
|
||||||
|
failed(FatalBackendException(e))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAuthInteractiveCallback(context: Context, cloud: OnedriveCloud?, success: (cloud: OnedriveCloud) -> Unit, failed: (e: FatalBackendException) -> Unit): AuthenticationCallback {
|
||||||
|
return object : AuthenticationCallback {
|
||||||
|
|
||||||
|
override fun onSuccess(authenticationResult: IAuthenticationResult) {
|
||||||
|
onTokenObtained(context, cloud, authenticationResult, success)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(e: MsalException) {
|
||||||
|
Timber.tag("AuthenticateCloudPresenter").e(e, "Successfully authenticated")
|
||||||
|
failed(FatalBackendException(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCancel() {
|
||||||
|
Timber.tag("AuthenticateCloudPresenter").i("User cancelled login")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
|||||||
|
package org.cryptomator.presentation.presenter
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential
|
||||||
|
import com.google.api.services.drive.DriveScopes
|
||||||
|
|
||||||
|
object GoogleAuthHelper {
|
||||||
|
|
||||||
|
fun getChooseAccountIntent(context: Context): Intent? {
|
||||||
|
return GoogleAccountCredential.usingOAuth2(context, setOf(DriveScopes.DRIVE)).newChooseAccountIntent()
|
||||||
|
}
|
||||||
|
}
|
@ -5,4 +5,39 @@
|
|||||||
<!-- Required to self update Cryptomator in the apkstore variant -->
|
<!-- Required to self update Cryptomator in the apkstore variant -->
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
|
|
||||||
|
<!-- Keep in sync with apkstore, playstore and fdroid -->
|
||||||
|
<application>
|
||||||
|
<activity
|
||||||
|
android:name="com.dropbox.core.android.AuthActivity"
|
||||||
|
android:configChanges="orientation|keyboard"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleTask">
|
||||||
|
<intent-filter>
|
||||||
|
<data android:scheme="db-${DROPBOX_API_KEY}" />
|
||||||
|
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="com.microsoft.identity.client.BrowserTabActivity"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data
|
||||||
|
android:host="org.cryptomator"
|
||||||
|
android:path="/${ONEDRIVE_API_KEY_DECODED}"
|
||||||
|
android:scheme="msauth" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/ic_launcher_background" />
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/ic_launcher_background" />
|
|
||||||
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
|
|
||||||
</adaptive-icon>
|
|
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 17 KiB |
@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="ic_launcher_background">#F1C40F</color>
|
<color name="ic_launcher_background">#F1C40F</color>
|
||||||
</resources>
|
</resources>
|
43
presentation/src/fdroid/AndroidManifest.xml
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="org.cryptomator.presentation">
|
||||||
|
|
||||||
|
<!-- Required to self update Cryptomator in the apkstore variant -->
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
|
|
||||||
|
<!-- Keep in sync with apkstore, playstore and fdroid -->
|
||||||
|
<application>
|
||||||
|
<activity
|
||||||
|
android:name="com.dropbox.core.android.AuthActivity"
|
||||||
|
android:configChanges="orientation|keyboard"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleTask">
|
||||||
|
<intent-filter>
|
||||||
|
<data android:scheme="db-${DROPBOX_API_KEY}" />
|
||||||
|
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="com.microsoft.identity.client.BrowserTabActivity"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data
|
||||||
|
android:host="org.cryptomator"
|
||||||
|
android:path="/${ONEDRIVE_API_KEY_DECODED}"
|
||||||
|
android:scheme="msauth" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
@ -0,0 +1,11 @@
|
|||||||
|
package org.cryptomator.presentation.presenter
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
|
||||||
|
object GoogleAuthHelper {
|
||||||
|
|
||||||
|
fun getChooseAccountIntent(context: Context): Intent? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
@ -1,610 +0,0 @@
|
|||||||
package org.cryptomator.presentation.presenter
|
|
||||||
|
|
||||||
import android.accounts.AccountManager
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.Intent.ACTION_OPEN_DOCUMENT_TREE
|
|
||||||
import android.provider.DocumentsContract
|
|
||||||
import android.widget.Toast
|
|
||||||
import com.dropbox.core.android.Auth
|
|
||||||
import com.microsoft.identity.client.AuthenticationCallback
|
|
||||||
import com.microsoft.identity.client.IAccount
|
|
||||||
import com.microsoft.identity.client.IAuthenticationResult
|
|
||||||
import com.microsoft.identity.client.IMultipleAccountPublicClientApplication
|
|
||||||
import com.microsoft.identity.client.IPublicClientApplication
|
|
||||||
import com.microsoft.identity.client.PublicClientApplication
|
|
||||||
import com.microsoft.identity.client.exception.MsalClientException
|
|
||||||
import com.microsoft.identity.client.exception.MsalException
|
|
||||||
import com.microsoft.identity.client.exception.MsalServiceException
|
|
||||||
import com.microsoft.identity.client.exception.MsalUiRequiredException
|
|
||||||
import org.cryptomator.data.util.X509CertificateHelper
|
|
||||||
import org.cryptomator.domain.Cloud
|
|
||||||
import org.cryptomator.domain.CloudType
|
|
||||||
import org.cryptomator.domain.DropboxCloud
|
|
||||||
import org.cryptomator.domain.GoogleDriveCloud
|
|
||||||
import org.cryptomator.domain.OnedriveCloud
|
|
||||||
import org.cryptomator.domain.PCloud
|
|
||||||
import org.cryptomator.domain.WebDavCloud
|
|
||||||
import org.cryptomator.domain.di.PerView
|
|
||||||
import org.cryptomator.domain.exception.FatalBackendException
|
|
||||||
import org.cryptomator.domain.exception.NetworkConnectionException
|
|
||||||
import org.cryptomator.domain.exception.authentication.AuthenticationException
|
|
||||||
import org.cryptomator.domain.exception.authentication.WebDavCertificateUntrustedAuthenticationException
|
|
||||||
import org.cryptomator.domain.exception.authentication.WebDavNotSupportedException
|
|
||||||
import org.cryptomator.domain.exception.authentication.WebDavServerNotFoundException
|
|
||||||
import org.cryptomator.domain.exception.authentication.WrongCredentialsException
|
|
||||||
import org.cryptomator.domain.usecases.cloud.AddOrChangeCloudConnectionUseCase
|
|
||||||
import org.cryptomator.domain.usecases.cloud.GetCloudsUseCase
|
|
||||||
import org.cryptomator.domain.usecases.cloud.GetUsernameUseCase
|
|
||||||
import org.cryptomator.generator.Callback
|
|
||||||
import org.cryptomator.presentation.BuildConfig
|
|
||||||
import org.cryptomator.presentation.R
|
|
||||||
import org.cryptomator.presentation.exception.ExceptionHandlers
|
|
||||||
import org.cryptomator.presentation.exception.PermissionNotGrantedException
|
|
||||||
import org.cryptomator.presentation.intent.AuthenticateCloudIntent
|
|
||||||
import org.cryptomator.presentation.intent.Intents
|
|
||||||
import org.cryptomator.presentation.model.CloudModel
|
|
||||||
import org.cryptomator.presentation.model.CloudTypeModel
|
|
||||||
import org.cryptomator.presentation.model.LocalStorageModel
|
|
||||||
import org.cryptomator.presentation.model.ProgressModel
|
|
||||||
import org.cryptomator.presentation.model.ProgressStateModel
|
|
||||||
import org.cryptomator.presentation.model.S3CloudModel
|
|
||||||
import org.cryptomator.presentation.model.WebDavCloudModel
|
|
||||||
import org.cryptomator.presentation.model.mappers.CloudModelMapper
|
|
||||||
import org.cryptomator.presentation.ui.activity.view.AuthenticateCloudView
|
|
||||||
import org.cryptomator.presentation.workflow.ActivityResult
|
|
||||||
import org.cryptomator.presentation.workflow.AddExistingVaultWorkflow
|
|
||||||
import org.cryptomator.presentation.workflow.CreateNewVaultWorkflow
|
|
||||||
import org.cryptomator.presentation.workflow.Workflow
|
|
||||||
import org.cryptomator.util.ExceptionUtil
|
|
||||||
import org.cryptomator.util.crypto.CredentialCryptor
|
|
||||||
import java.security.cert.CertificateEncodingException
|
|
||||||
import java.security.cert.CertificateException
|
|
||||||
import java.security.cert.X509Certificate
|
|
||||||
import javax.inject.Inject
|
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
@PerView
|
|
||||||
class AuthenticateCloudPresenter @Inject constructor( //
|
|
||||||
exceptionHandlers: ExceptionHandlers, //
|
|
||||||
private val cloudModelMapper: CloudModelMapper, //
|
|
||||||
private val addOrChangeCloudConnectionUseCase: AddOrChangeCloudConnectionUseCase, //
|
|
||||||
private val getCloudsUseCase: GetCloudsUseCase, //
|
|
||||||
private val getUsernameUseCase: GetUsernameUseCase, //
|
|
||||||
private val addExistingVaultWorkflow: AddExistingVaultWorkflow, //
|
|
||||||
private val createNewVaultWorkflow: CreateNewVaultWorkflow
|
|
||||||
) : Presenter<AuthenticateCloudView>(exceptionHandlers) {
|
|
||||||
|
|
||||||
private val strategies = arrayOf( //
|
|
||||||
DropboxAuthStrategy(), //
|
|
||||||
OnedriveAuthStrategy(), //
|
|
||||||
PCloudAuthStrategy(), //
|
|
||||||
WebDAVAuthStrategy(), //
|
|
||||||
S3AuthStrategy(), //
|
|
||||||
LocalStorageAuthStrategy() //
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun workflows(): Iterable<Workflow<*>> {
|
|
||||||
return listOf(createNewVaultWorkflow, addExistingVaultWorkflow)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun resumed() {
|
|
||||||
val cloud = view?.intent()?.cloud()
|
|
||||||
val error = view?.intent()?.error()
|
|
||||||
handleNetworkConnectionExceptionIfRequired(error)
|
|
||||||
view?.intent()?.let { cloud?.let { cloud -> authStrategyFor(cloud).resumed(it) } }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleNetworkConnectionExceptionIfRequired(error: AuthenticationException?) {
|
|
||||||
if (error != null && ExceptionUtil.contains(error, NetworkConnectionException::class.java)) {
|
|
||||||
view?.showMessage(R.string.error_no_network_connection)
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun authStrategyFor(cloud: CloudModel): AuthStrategy {
|
|
||||||
strategies.forEach { strategy ->
|
|
||||||
if (strategy.supports(cloud)) {
|
|
||||||
return strategy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return FailingAuthStrategy()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getUsernameAndSuceedAuthentication(cloud: Cloud) {
|
|
||||||
getUsernameUseCase.withCloud(cloud).run(object : DefaultResultHandler<String>() {
|
|
||||||
override fun onSuccess(username: String) {
|
|
||||||
succeedAuthenticationWith(updateUsernameOf(cloud, username))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onError(e: Throwable) {
|
|
||||||
super.onError(e)
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateUsernameOf(cloud: Cloud, username: String): Cloud {
|
|
||||||
when (cloud.type()) {
|
|
||||||
CloudType.DROPBOX -> return DropboxCloud.aCopyOf(cloud as DropboxCloud).withUsername(username).build()
|
|
||||||
CloudType.ONEDRIVE -> return OnedriveCloud.aCopyOf(cloud as OnedriveCloud).withUsername(username).build()
|
|
||||||
}
|
|
||||||
throw IllegalStateException("Cloud " + cloud.type() + " is not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun succeedAuthenticationWith(cloud: Cloud) {
|
|
||||||
addOrChangeCloudConnectionUseCase //
|
|
||||||
.withCloud(cloud) //
|
|
||||||
.run(object : DefaultResultHandler<Void?>() {
|
|
||||||
override fun onSuccess(void: Void?) {
|
|
||||||
finishWithResult(cloudModelMapper.toModel(cloud))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onError(e: Throwable) {
|
|
||||||
super.onError(e)
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun failAuthentication(cloudName: Int) {
|
|
||||||
activity().runOnUiThread {
|
|
||||||
view?.showMessage(String.format(getString(R.string.screen_authenticate_auth_authentication_failed), getString(cloudName)))
|
|
||||||
}
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun failAuthentication(error: PermissionNotGrantedException) {
|
|
||||||
finishWithResult(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class DropboxAuthStrategy : AuthStrategy {
|
|
||||||
|
|
||||||
private var authenticationStarted = false
|
|
||||||
override fun supports(cloud: CloudModel): Boolean {
|
|
||||||
return cloud.cloudType() == CloudTypeModel.DROPBOX
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun resumed(intent: AuthenticateCloudIntent) {
|
|
||||||
if (authenticationStarted) {
|
|
||||||
handleAuthenticationResult(intent.cloud())
|
|
||||||
} else {
|
|
||||||
startAuthentication()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startAuthentication() {
|
|
||||||
showProgress(ProgressModel(ProgressStateModel.AUTHENTICATION))
|
|
||||||
authenticationStarted = true
|
|
||||||
Auth.startOAuth2Authentication(context(), BuildConfig.DROPBOX_API_KEY)
|
|
||||||
view?.skipTransition()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleAuthenticationResult(cloudModel: CloudModel) {
|
|
||||||
val authToken = Auth.getOAuth2Token()
|
|
||||||
if (authToken == null) {
|
|
||||||
failAuthentication(cloudModel.name())
|
|
||||||
} else {
|
|
||||||
getUsernameAndSuceedAuthentication( //
|
|
||||||
DropboxCloud.aCopyOf(cloudModel.toCloud() as DropboxCloud) //
|
|
||||||
.withAccessToken(encrypt(authToken)) //
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Callback(dispatchResultOkOnly = false)
|
|
||||||
fun onUserRecoveryFinished(result: ActivityResult, cloud: CloudModel) {
|
|
||||||
if (result.isResultOk) {
|
|
||||||
succeedAuthenticationWith(cloud.toCloud())
|
|
||||||
} else {
|
|
||||||
failAuthentication(cloud.name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Callback(dispatchResultOkOnly = false)
|
|
||||||
fun onGoogleDriveAuthenticated(result: ActivityResult, cloud: CloudModel) {
|
|
||||||
if (result.isResultOk) {
|
|
||||||
val accountName = result.intent()?.extras?.getString(AccountManager.KEY_ACCOUNT_NAME)
|
|
||||||
succeedAuthenticationWith(
|
|
||||||
GoogleDriveCloud.aCopyOf(cloud.toCloud() as GoogleDriveCloud) //
|
|
||||||
.withUsername(accountName) //
|
|
||||||
.withAccessToken(accountName) //
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
failAuthentication(cloud.name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class OnedriveAuthStrategy : AuthStrategy {
|
|
||||||
|
|
||||||
private var authenticationStarted = false
|
|
||||||
override fun supports(cloud: CloudModel): Boolean {
|
|
||||||
return cloud.cloudType() == CloudTypeModel.ONEDRIVE
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun resumed(intent: AuthenticateCloudIntent) {
|
|
||||||
if (!authenticationStarted) {
|
|
||||||
startAuthentication(intent.cloud())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startAuthentication(cloud: CloudModel) {
|
|
||||||
authenticationStarted = true
|
|
||||||
|
|
||||||
Toast.makeText(context(), R.string.notification_authenticating, Toast.LENGTH_SHORT).show()
|
|
||||||
|
|
||||||
PublicClientApplication.createMultipleAccountPublicClientApplication(
|
|
||||||
context(),
|
|
||||||
R.raw.auth_config_onedrive,
|
|
||||||
object : IPublicClientApplication.IMultipleAccountApplicationCreatedListener {
|
|
||||||
override fun onCreated(application: IMultipleAccountPublicClientApplication) {
|
|
||||||
application.getAccounts(object : IPublicClientApplication.LoadAccountsCallback {
|
|
||||||
override fun onTaskCompleted(accounts: List<IAccount>) {
|
|
||||||
if (accounts.isEmpty()) {
|
|
||||||
application.acquireToken(activity(), onedriveScopes(), getAuthInteractiveCallback(cloud))
|
|
||||||
} else {
|
|
||||||
accounts.find { account -> account.username == cloud.username() }?.let {
|
|
||||||
application.acquireTokenSilentAsync(
|
|
||||||
onedriveScopes(),
|
|
||||||
it,
|
|
||||||
"https://login.microsoftonline.com/common",
|
|
||||||
getAuthSilentCallback(cloud, application)
|
|
||||||
)
|
|
||||||
} ?: application.acquireToken(activity(), onedriveScopes(), getAuthInteractiveCallback(cloud))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onError(e: MsalException) {
|
|
||||||
Timber.tag("AuthenticateCloudPresenter").e(e, "Error to get accounts")
|
|
||||||
failAuthentication(cloud.name())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onError(e: MsalException) {
|
|
||||||
Timber.tag("AuthenticateCloudPresenter").i(e, "Error in configuration")
|
|
||||||
failAuthentication(cloud.name())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getAuthSilentCallback(cloud: CloudModel, application: IMultipleAccountPublicClientApplication): AuthenticationCallback {
|
|
||||||
return object : AuthenticationCallback {
|
|
||||||
|
|
||||||
override fun onSuccess(authenticationResult: IAuthenticationResult) {
|
|
||||||
Timber.tag("AuthenticateCloudPresenter").i("Successfully authenticated")
|
|
||||||
handleAuthenticationResult(cloud, authenticationResult.accessToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onError(e: MsalException) {
|
|
||||||
Timber.tag("AuthenticateCloudPresenter").e(e, "Failed to acquireToken")
|
|
||||||
when (e) {
|
|
||||||
is MsalClientException -> {
|
|
||||||
/* Exception inside MSAL, more info inside MsalError.java */
|
|
||||||
failAuthentication(cloud.name())
|
|
||||||
}
|
|
||||||
is MsalServiceException -> {
|
|
||||||
/* Exception when communicating with the STS, likely config issue */
|
|
||||||
failAuthentication(cloud.name())
|
|
||||||
}
|
|
||||||
is MsalUiRequiredException -> {
|
|
||||||
/* Tokens expired or no session, retry with interactive */
|
|
||||||
application.acquireToken(activity(), onedriveScopes(), getAuthInteractiveCallback(cloud))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCancel() {
|
|
||||||
Timber.tag("AuthenticateCloudPresenter").i("User cancelled login")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getAuthInteractiveCallback(cloud: CloudModel): AuthenticationCallback {
|
|
||||||
return object : AuthenticationCallback {
|
|
||||||
|
|
||||||
override fun onSuccess(authenticationResult: IAuthenticationResult) {
|
|
||||||
Timber.tag("AuthenticateCloudPresenter").i("Successfully authenticated")
|
|
||||||
handleAuthenticationResult(cloud, authenticationResult.accessToken, authenticationResult.account.username)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onError(e: MsalException) {
|
|
||||||
Timber.tag("AuthenticateCloudPresenter").e(e, "Successfully authenticated")
|
|
||||||
failAuthentication(cloud.name())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCancel() {
|
|
||||||
Timber.tag("AuthenticateCloudPresenter").i("User cancelled login")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleAuthenticationResult(cloud: CloudModel, accessToken: String) {
|
|
||||||
getUsernameAndSuceedAuthentication( //
|
|
||||||
OnedriveCloud.aCopyOf(cloud.toCloud() as OnedriveCloud) //
|
|
||||||
.withAccessToken(encrypt(accessToken)) //
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleAuthenticationResult(cloud: CloudModel, accessToken: String, username: String) {
|
|
||||||
getUsernameAndSuceedAuthentication( //
|
|
||||||
OnedriveCloud.aCopyOf(cloud.toCloud() as OnedriveCloud) //
|
|
||||||
.withAccessToken(encrypt(accessToken)) //
|
|
||||||
.withUsername(username)
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class PCloudAuthStrategy : AuthStrategy {
|
|
||||||
|
|
||||||
private var authenticationStarted = false
|
|
||||||
|
|
||||||
override fun supports(cloud: CloudModel): Boolean {
|
|
||||||
return cloud.cloudType() == CloudTypeModel.PCLOUD
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun resumed(intent: AuthenticateCloudIntent) {
|
|
||||||
if (authenticationStarted) {
|
|
||||||
finish()
|
|
||||||
} else {
|
|
||||||
startAuthentication(intent.cloud())
|
|
||||||
Toast.makeText(
|
|
||||||
context(),
|
|
||||||
String.format(getString(R.string.error_authentication_failed_re_authenticate), intent.cloud().username()),
|
|
||||||
Toast.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startAuthentication(cloud: CloudModel) {
|
|
||||||
authenticationStarted = true
|
|
||||||
showProgress(ProgressModel(ProgressStateModel.AUTHENTICATION))
|
|
||||||
view?.skipTransition()
|
|
||||||
requestActivityResult(
|
|
||||||
ActivityResultCallbacks.pCloudReAuthenticationFinished(cloud), //
|
|
||||||
Intents.cloudConnectionListIntent() //
|
|
||||||
.withCloudType(CloudTypeModel.PCLOUD) //
|
|
||||||
.withDialogTitle(context().getString(R.string.screen_update_pcloud_connections_title)) //
|
|
||||||
.withFinishOnCloudItemClick(false) //
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Callback
|
|
||||||
fun pCloudReAuthenticationFinished(activityResult: ActivityResult, cloud: CloudModel) {
|
|
||||||
val code = activityResult.intent().extras?.getString(CloudConnectionListPresenter.PCLOUD_OAUTH_AUTH_CODE, "")
|
|
||||||
val hostname = activityResult.intent().extras?.getString(CloudConnectionListPresenter.PCLOUD_HOSTNAME, "")
|
|
||||||
|
|
||||||
if (!code.isNullOrEmpty() && !hostname.isNullOrEmpty()) {
|
|
||||||
Timber.tag("CloudConnectionListPresenter").i("PCloud OAuth code successfully retrieved")
|
|
||||||
|
|
||||||
val accessToken = CredentialCryptor //
|
|
||||||
.getInstance(this.context()) //
|
|
||||||
.encrypt(code)
|
|
||||||
val pCloudSkeleton = PCloud.aPCloud() //
|
|
||||||
.withAccessToken(accessToken)
|
|
||||||
.withUrl(hostname)
|
|
||||||
.build();
|
|
||||||
getUsernameUseCase //
|
|
||||||
.withCloud(pCloudSkeleton) //
|
|
||||||
.run(object : DefaultResultHandler<String>() {
|
|
||||||
override fun onSuccess(username: String) {
|
|
||||||
Timber.tag("CloudConnectionListPresenter").i("PCloud Authentication successfully")
|
|
||||||
prepareForSavingPCloud(PCloud.aCopyOf(pCloudSkeleton).withUsername(username).build())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Timber.tag("CloudConnectionListPresenter").i("PCloud Authentication not successful")
|
|
||||||
failAuthentication(cloud.name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun prepareForSavingPCloud(cloud: PCloud) {
|
|
||||||
getCloudsUseCase //
|
|
||||||
.withCloudType(cloud.type()) //
|
|
||||||
.run(object : DefaultResultHandler<List<Cloud>>() {
|
|
||||||
override fun onSuccess(clouds: List<Cloud>) {
|
|
||||||
clouds.firstOrNull {
|
|
||||||
(it as PCloud).username() == cloud.username()
|
|
||||||
}?.let {
|
|
||||||
it as PCloud
|
|
||||||
succeedAuthenticationWith(
|
|
||||||
PCloud.aCopyOf(it) //
|
|
||||||
.withUrl(cloud.url())
|
|
||||||
.withAccessToken(cloud.accessToken())
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
} ?: succeedAuthenticationWith(cloud)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class WebDAVAuthStrategy : AuthStrategy {
|
|
||||||
|
|
||||||
override fun supports(cloud: CloudModel): Boolean {
|
|
||||||
return cloud.cloudType() == CloudTypeModel.WEBDAV
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun resumed(intent: AuthenticateCloudIntent) {
|
|
||||||
handleWebDavAuthenticationExceptionIfRequired(intent.cloud() as WebDavCloudModel, intent.error())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleWebDavAuthenticationExceptionIfRequired(cloud: WebDavCloudModel, e: AuthenticationException) {
|
|
||||||
Timber.tag("AuthicateCloudPrester").e(e)
|
|
||||||
when {
|
|
||||||
ExceptionUtil.contains(e, WrongCredentialsException::class.java) -> {
|
|
||||||
failAuthentication(cloud.name())
|
|
||||||
}
|
|
||||||
ExceptionUtil.contains(e, WebDavCertificateUntrustedAuthenticationException::class.java) -> {
|
|
||||||
handleCertificateUntrustedExceptionIfRequired(cloud, e)
|
|
||||||
}
|
|
||||||
ExceptionUtil.contains(e, WebDavServerNotFoundException::class.java) -> {
|
|
||||||
view?.showMessage(R.string.error_server_not_found)
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
ExceptionUtil.contains(e, WebDavNotSupportedException::class.java) -> {
|
|
||||||
view?.showMessage(R.string.screen_cloud_error_webdav_not_supported)
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleCertificateUntrustedExceptionIfRequired(cloud: WebDavCloudModel, e: AuthenticationException) {
|
|
||||||
val untrustedException = ExceptionUtil.extract(e, WebDavCertificateUntrustedAuthenticationException::class.java)
|
|
||||||
try {
|
|
||||||
val certificate = X509CertificateHelper.convertFromPem(untrustedException.get().certificate)
|
|
||||||
view?.showUntrustedCertificateDialog(cloud.toCloud() as WebDavCloud, certificate)
|
|
||||||
} catch (ex: CertificateException) {
|
|
||||||
Timber.tag("AuthicateCloudPrester").e(ex)
|
|
||||||
throw FatalBackendException(ex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onAcceptWebDavCertificateClicked(cloud: WebDavCloud, certificate: X509Certificate) {
|
|
||||||
try {
|
|
||||||
val webDavCloudWithAcceptedCert = WebDavCloud.aCopyOf(cloud) //
|
|
||||||
.withCertificate(X509CertificateHelper.convertToPem(certificate)) //
|
|
||||||
.build()
|
|
||||||
finishWithResultAndExtra(
|
|
||||||
cloudModelMapper.toModel(webDavCloudWithAcceptedCert), //
|
|
||||||
WEBDAV_ACCEPTED_UNTRUSTED_CERTIFICATE, //
|
|
||||||
true
|
|
||||||
)
|
|
||||||
} catch (e: CertificateEncodingException) {
|
|
||||||
Timber.tag("AuthicateCloudPrester").e(e)
|
|
||||||
throw FatalBackendException(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onAcceptWebDavCertificateDenied() {
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class S3AuthStrategy : AuthStrategy {
|
|
||||||
|
|
||||||
private var authenticationStarted = false
|
|
||||||
|
|
||||||
override fun supports(cloud: CloudModel): Boolean {
|
|
||||||
return cloud.cloudType() == CloudTypeModel.S3
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun resumed(intent: AuthenticateCloudIntent) {
|
|
||||||
when {
|
|
||||||
ExceptionUtil.contains(intent.error(), WrongCredentialsException::class.java) -> {
|
|
||||||
if (!authenticationStarted) {
|
|
||||||
startAuthentication(intent.cloud())
|
|
||||||
Toast.makeText(
|
|
||||||
context(),
|
|
||||||
String.format(getString(R.string.error_authentication_failed), intent.cloud().username()),
|
|
||||||
Toast.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
Timber.tag("AuthicateCloudPrester").e(intent.error())
|
|
||||||
failAuthentication(intent.cloud().name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startAuthentication(cloud: CloudModel) {
|
|
||||||
authenticationStarted = true
|
|
||||||
startIntent(Intents.s3AddOrChangeIntent().withS3Cloud(cloud as S3CloudModel))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class LocalStorageAuthStrategy : AuthStrategy {
|
|
||||||
|
|
||||||
private var authenticationStarted = false
|
|
||||||
|
|
||||||
override fun supports(cloud: CloudModel): Boolean {
|
|
||||||
return cloud.cloudType() == CloudTypeModel.LOCAL
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun resumed(intent: AuthenticateCloudIntent) {
|
|
||||||
if (!authenticationStarted) {
|
|
||||||
startAuthentication(intent.cloud())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startAuthentication(cloud: CloudModel) {
|
|
||||||
authenticationStarted = true
|
|
||||||
|
|
||||||
val uri = (cloud as LocalStorageModel).uri()
|
|
||||||
|
|
||||||
val permissions = context().contentResolver.persistedUriPermissions
|
|
||||||
for (permission in permissions) {
|
|
||||||
if (permission.uri.toString() == uri) {
|
|
||||||
succeedAuthenticationWith(cloud.toCloud())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timber.tag("AuthicateCloudPrester").e("Permission revoked, ask to re-pick location")
|
|
||||||
|
|
||||||
Toast.makeText(context(), getString(R.string.permission_revoked_re_request_permission), Toast.LENGTH_LONG).show()
|
|
||||||
|
|
||||||
val openDocumentTree = Intent(ACTION_OPEN_DOCUMENT_TREE).apply {
|
|
||||||
putExtra(DocumentsContract.EXTRA_INITIAL_URI, uri)
|
|
||||||
}
|
|
||||||
|
|
||||||
requestActivityResult(ActivityResultCallbacks.rePickedLocalStorageLocation(cloud), openDocumentTree)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Callback
|
|
||||||
fun rePickedLocalStorageLocation(result: ActivityResult, cloud: LocalStorageModel) {
|
|
||||||
val rootTreeUriOfLocalStorage = result.intent().data
|
|
||||||
rootTreeUriOfLocalStorage?.let {
|
|
||||||
context() //
|
|
||||||
.contentResolver //
|
|
||||||
.takePersistableUriPermission( //
|
|
||||||
it, //
|
|
||||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Timber.tag("AuthicateCloudPrester").e("Permission granted again")
|
|
||||||
succeedAuthenticationWith(cloud.toCloud())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun encrypt(password: String): String {
|
|
||||||
return CredentialCryptor //
|
|
||||||
.getInstance(context()) //
|
|
||||||
.encrypt(password)
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class FailingAuthStrategy : AuthStrategy {
|
|
||||||
|
|
||||||
override fun supports(cloud: CloudModel): Boolean {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun resumed(intent: AuthenticateCloudIntent) {
|
|
||||||
view?.showError(R.string.error_authentication_failed)
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private interface AuthStrategy {
|
|
||||||
|
|
||||||
fun supports(cloud: CloudModel): Boolean
|
|
||||||
fun resumed(intent: AuthenticateCloudIntent)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
const val WEBDAV_ACCEPTED_UNTRUSTED_CERTIFICATE = "acceptedUntrustedCertificate"
|
|
||||||
|
|
||||||
fun onedriveScopes(): Array<String> {
|
|
||||||
return arrayOf("User.Read", "Files.ReadWrite")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
unsubscribeOnDestroy(addOrChangeCloudConnectionUseCase, getCloudsUseCase, getUsernameUseCase)
|
|
||||||
}
|
|
||||||
}
|
|
13
presentation/src/lite/AndroidManifest.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="org.cryptomator.presentation">
|
||||||
|
|
||||||
|
<!-- Required to send intent to F-Droid app -->
|
||||||
|
<queries>
|
||||||
|
<package android:name="org.fdroid.fdroid" />
|
||||||
|
</queries>
|
||||||
|
|
||||||
|
<!-- Required to self update Cryptomator in the F-Droid main variant -->
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
|
|
||||||
|
</manifest>
|
@ -0,0 +1,15 @@
|
|||||||
|
package org.cryptomator.presentation.presenter
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
|
||||||
|
object DropboxAuthHelper {
|
||||||
|
|
||||||
|
fun startOAuth2Authentication(context: Context) {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getOAuth2Token(): String? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package org.cryptomator.presentation.presenter
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import org.cryptomator.domain.OnedriveCloud
|
||||||
|
import org.cryptomator.domain.exception.FatalBackendException
|
||||||
|
|
||||||
|
object OnedriveAuthentication {
|
||||||
|
|
||||||
|
fun getAuthenticatedOnedriveCloud(activity: Activity, success: (cloud: OnedriveCloud) -> Unit, failed: (e: FatalBackendException) -> Unit) {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refreshOrCheckAuth(activity: Activity, cloud: OnedriveCloud, success: (cloud: OnedriveCloud) -> Unit, failed: (e: FatalBackendException) -> Unit) {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<group android:scaleX="0.05172973"
|
||||||
|
android:scaleY="0.05172973"
|
||||||
|
android:translateX="25.29"
|
||||||
|
android:translateY="29.480108">
|
||||||
|
<path
|
||||||
|
android:pathData="m877,689.3a51.6,51.6 0,0 0,-23.7 -5.8h-40.5c27.8,-41.7 46.3,-92.3 46.3,-153.2 0.1,-34.3 -5.4,-61.2 -10.5,-78.8a83.6,83.6 0,0 1,-0.6 -45.6c14.7,-53.1 5.1,-111.8 5.1,-111.8 -185.1,-57.8 -300.1,-0.5 -300.1,-0.5s-114.8,-57.6 -300,-0.4c0,0 -9.9,58.6 4.7,111.8a83.7,83.7 0,0 1,-0.7 45.6c-5.1,17.6 -10.7,44.5 -10.8,78.8 -0.1,61.3 18.4,112.2 46.4,154.1h-41.3a51.6,51.6 0,0 0,-23.7 5.8c-19.8,10.1 -35.9,32.7 -45.3,63.7 -8.7,28.8 -11,62.1 -6.5,93.9 8.5,59.3 38.8,99.2 75.5,99.2h103.5v-0.1c4.4,-0.2 8.8,-2.2 12.9,-5.9l49.7,-43.8c10.2,-9.1 17.8,-32 20.2,-61.3a248,248 0,0 0,0.7 -25.9c61.4,33.1 113.8,46.2 113.8,46.2s52.5,-13 114.1,-46a245.8,245.8 0,0 0,0.7 25.7c2.4,29.3 10,52.2 20.2,61.3l49.7,43.8c4.2,3.7 8.5,5.6 12.9,5.9v0.1h103.5c36.7,0 67,-39.8 75.5,-99.2 4.6,-31.8 2.3,-65.2 -6.5,-93.9 -9.4,-30.9 -25.5,-53.5 -45.4,-63.7zM404.2,881.4c-0.5,0.3 -25.9,22.9 -40.4,35.7a11.2,11.2 0,0 1,-17.4 -3.3c-21,-41.1 -23.3,-124.9 -8.4,-176.5 25.2,24.3 53.1,44.2 80.1,60.3 1.5,30.4 -4.3,78.1 -14,83.7zM597.1,670.3 L552.4,683 507.7,670.2 540,534.7a57.5,57.5 0,1 1,25.3 0zM758.5,913.8a11.2,11.2 0,0 1,-17.4 3.3c-14.5,-12.8 -39.9,-35.4 -40.4,-35.7 -9.6,-5.6 -15.5,-53.1 -14,-83.5 27.1,-16.1 55,-36 80.3,-60.2 14.8,51.6 12.5,135.1 -8.5,176.1z"
|
||||||
|
android:fillColor="#49b04a"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m178.9,584.1 l20.6,-40.4a49.4,49.4 0,0 0,-0.5 -46l18.1,-35.3a74.8,74.8 0,0 0,16.4 -0.6c1.4,-6.4 2.9,-12.1 4.3,-16.9a63.6,63.6 0,0 0,0.6 -34.7c-9.9,-36.1 -9.5,-73.8 -7.7,-97.3a75.1,75.1 0,0 0,-68.6 119.1l-19.3,37.7a49.3,49.3 0,0 0,-36.5 26.3l-20.6,40.4a49.3,49.3 0,0 0,-0.5 43.9,82.8 82.8,0 0,0 -74.8,122.8 20,20 0,0 0,35 -19.3,42.8 42.8,0 1,1 42.2,21.8 20,20 0,0 0,2.2 39.9,21.8 21.8,0 0,0 2.3,-0.1 82.8,82.8 0,0 0,54 -135.7,49.3 49.3,0 0,0 32.8,-25.6z"
|
||||||
|
android:fillColor="#49b04a"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m1099,619.4a82.9,82.9 0,0 0,-73.6 -45.1,49.3 49.3,0 0,0 -3,-37.9l-20.6,-40.4a49.3,49.3 0,0 0,-36.5 -26.3l-19.2,-37.7a75,75 0,0 0,-70.7 -118.8c1.7,23.4 2.1,61.6 -8,97.9a63.6,63.6 0,0 0,0.5 34.7c1.3,4.5 2.7,9.7 4,15.5a75.4,75.4 0,0 0,19.2 1l18.1,35.3a49.4,49.4 0,0 0,-0.5 46l20.6,40.4a49.2,49.2 0,0 0,29 24.6,82.8 82.8,0 0,0 57.8,130.7 22.1,22.1 0,0 0,2.3 0.1,20 20,0 0,0 2.2,-39.9 42.8,42.8 0,1 1,42.2 -21.8,20 20,0 1,0 35,19.3 82.8,82.8 0,0 0,1.3 -77.7z"
|
||||||
|
android:fillColor="#49b04a"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m553.1,271.7c19,-7.6 67.6,-23.5 139.3,-23.5 39.7,0 80.5,4.9 122,14.5 -9.8,-133.1 -112.4,-262.7 -261.7,-262.7 -160.5,0 -252.7,129.2 -261.7,262.1 41.3,-9.5 81.9,-14.4 121.4,-14.4 72.5,0 121.5,16.2 140.7,24zM623.1,216.9a11.5,11.5 0,1 1,11.5 -11.5,11.5 11.5,0 0,1 -11.5,11.5zM646.4,107.1a60.6,60.6 0,0 1,60.6 60.6h-121.2a60.6,60.6 0,0 1,60.6 -60.6zM577.8,193.9a11.5,11.5 0,1 1,-11.5 11.5,11.5 11.5,0 0,1 11.6,-11.5zM487.2,216.9a11.5,11.5 0,1 1,11.5 -11.5,11.5 11.5,0 0,1 -11.5,11.5zM532.5,193.9a11.5,11.5 0,1 1,-11.5 11.5,11.5 11.5,0 0,1 11.6,-11.5zM459,107.1a60.6,60.6 0,0 1,60.6 60.6h-121.3a60.6,60.6 0,0 1,60.7 -60.6z"
|
||||||
|
android:fillColor="#49b04a"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#FFFFFF</color>
|
||||||
|
</resources>
|
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#F1C40F</color>
|
||||||
|
</resources>
|
@ -36,20 +36,17 @@
|
|||||||
android:required="false" />
|
android:required="false" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.activity.SplashActivity"
|
android:name=".ui.activity.VaultListActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:screenOrientation="portrait"
|
android:theme="@style/AppTheme.Starting"
|
||||||
android:theme="@style/SplashTheme">
|
android:windowSoftInputMode="adjustPan">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
|
||||||
android:name=".ui.activity.VaultListActivity"
|
|
||||||
android:exported="false"
|
|
||||||
android:windowSoftInputMode="adjustPan" />
|
|
||||||
<activity android:name=".ui.activity.ChooseCloudServiceActivity" />
|
<activity android:name=".ui.activity.ChooseCloudServiceActivity" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.activity.CreateVaultActivity"
|
android:name=".ui.activity.CreateVaultActivity"
|
||||||
@ -90,22 +87,6 @@
|
|||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:theme="@style/TransparentPopUp" />
|
android:theme="@style/TransparentPopUp" />
|
||||||
|
|
||||||
<!-- Cloud Services -->
|
|
||||||
<activity
|
|
||||||
android:name="com.dropbox.core.android.AuthActivity"
|
|
||||||
android:configChanges="orientation|keyboard"
|
|
||||||
android:exported="true"
|
|
||||||
android:launchMode="singleTask">
|
|
||||||
<intent-filter>
|
|
||||||
<data android:scheme="db-${DROPBOX_API_KEY}" />
|
|
||||||
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<!-- Settings -->
|
<!-- Settings -->
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.activity.AutoUploadChooseVaultActivity"
|
android:name=".ui.activity.AutoUploadChooseVaultActivity"
|
||||||
@ -119,6 +100,9 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".ui.activity.CloudSettingsActivity"
|
android:name=".ui.activity.CloudSettingsActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
<activity
|
||||||
|
android:name=".ui.activity.CryptomatorVariantsActivity"
|
||||||
|
android:exported="false" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.activity.LicensesActivity"
|
android:name=".ui.activity.LicensesActivity"
|
||||||
android:exported="true" />
|
android:exported="true" />
|
||||||
@ -188,22 +172,6 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name="com.microsoft.identity.client.BrowserTabActivity"
|
|
||||||
android:exported="true">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
|
|
||||||
<data
|
|
||||||
android:host="org.cryptomator"
|
|
||||||
android:path="/${ONEDRIVE_API_KEY_DECODED}"
|
|
||||||
android:scheme="msauth" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="${applicationId}.fileprovider"
|
android:authorities="${applicationId}.fileprovider"
|
||||||
|
@ -51,6 +51,9 @@ class CryptomatorApp : MultiDexApplication(), HasComponent<ApplicationComponent>
|
|||||||
"fdroid" -> {
|
"fdroid" -> {
|
||||||
"F-Droid Edition"
|
"F-Droid Edition"
|
||||||
}
|
}
|
||||||
|
"lite" -> {
|
||||||
|
"F-Droid Main Repo Edition"
|
||||||
|
}
|
||||||
else -> "Google Play Edition"
|
else -> "Google Play Edition"
|
||||||
}
|
}
|
||||||
Timber.tag("App").i(
|
Timber.tag("App").i(
|
||||||
|
@ -13,6 +13,7 @@ import org.cryptomator.presentation.ui.activity.ChooseCloudServiceActivity;
|
|||||||
import org.cryptomator.presentation.ui.activity.CloudConnectionListActivity;
|
import org.cryptomator.presentation.ui.activity.CloudConnectionListActivity;
|
||||||
import org.cryptomator.presentation.ui.activity.CloudSettingsActivity;
|
import org.cryptomator.presentation.ui.activity.CloudSettingsActivity;
|
||||||
import org.cryptomator.presentation.ui.activity.CreateVaultActivity;
|
import org.cryptomator.presentation.ui.activity.CreateVaultActivity;
|
||||||
|
import org.cryptomator.presentation.ui.activity.CryptomatorVariantsActivity;
|
||||||
import org.cryptomator.presentation.ui.activity.ImagePreviewActivity;
|
import org.cryptomator.presentation.ui.activity.ImagePreviewActivity;
|
||||||
import org.cryptomator.presentation.ui.activity.LicenseCheckActivity;
|
import org.cryptomator.presentation.ui.activity.LicenseCheckActivity;
|
||||||
import org.cryptomator.presentation.ui.activity.LicensesActivity;
|
import org.cryptomator.presentation.ui.activity.LicensesActivity;
|
||||||
@ -20,7 +21,6 @@ import org.cryptomator.presentation.ui.activity.S3AddOrChangeActivity;
|
|||||||
import org.cryptomator.presentation.ui.activity.SetPasswordActivity;
|
import org.cryptomator.presentation.ui.activity.SetPasswordActivity;
|
||||||
import org.cryptomator.presentation.ui.activity.SettingsActivity;
|
import org.cryptomator.presentation.ui.activity.SettingsActivity;
|
||||||
import org.cryptomator.presentation.ui.activity.SharedFilesActivity;
|
import org.cryptomator.presentation.ui.activity.SharedFilesActivity;
|
||||||
import org.cryptomator.presentation.ui.activity.SplashActivity;
|
|
||||||
import org.cryptomator.presentation.ui.activity.TextEditorActivity;
|
import org.cryptomator.presentation.ui.activity.TextEditorActivity;
|
||||||
import org.cryptomator.presentation.ui.activity.UnlockVaultActivity;
|
import org.cryptomator.presentation.ui.activity.UnlockVaultActivity;
|
||||||
import org.cryptomator.presentation.ui.activity.VaultListActivity;
|
import org.cryptomator.presentation.ui.activity.VaultListActivity;
|
||||||
@ -50,8 +50,6 @@ public interface ActivityComponent {
|
|||||||
|
|
||||||
Activity activity();
|
Activity activity();
|
||||||
|
|
||||||
void inject(SplashActivity splashActivity);
|
|
||||||
|
|
||||||
void inject(VaultListActivity vaultListActivity);
|
void inject(VaultListActivity vaultListActivity);
|
||||||
|
|
||||||
void inject(SetPasswordActivity setPasswordActivity);
|
void inject(SetPasswordActivity setPasswordActivity);
|
||||||
@ -123,4 +121,7 @@ public interface ActivityComponent {
|
|||||||
void inject(S3AddOrChangeActivity s3AddOrChangeActivity);
|
void inject(S3AddOrChangeActivity s3AddOrChangeActivity);
|
||||||
|
|
||||||
void inject(S3AddOrChangeFragment s3AddOrChangeFragment);
|
void inject(S3AddOrChangeFragment s3AddOrChangeFragment);
|
||||||
|
|
||||||
|
void inject(CryptomatorVariantsActivity cryptomatorVariantsActivity);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
package org.cryptomator.presentation.intent;
|
||||||
|
|
||||||
|
import org.cryptomator.generator.Intent;
|
||||||
|
import org.cryptomator.presentation.ui.activity.AutoUploadChooseVaultActivity;
|
||||||
|
|
||||||
|
@Intent(AutoUploadChooseVaultActivity.class)
|
||||||
|
public interface AutoUploadChooseVaultIntent {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package org.cryptomator.presentation.intent;
|
||||||
|
|
||||||
|
import org.cryptomator.generator.Intent;
|
||||||
|
import org.cryptomator.presentation.ui.activity.CryptomatorVariantsActivity;
|
||||||
|
|
||||||
|
@Intent(CryptomatorVariantsActivity.class)
|
||||||
|
public interface CryptomatorVariantsIntent {
|
||||||
|
|
||||||
|
}
|
@ -6,19 +6,6 @@ import android.content.Intent
|
|||||||
import android.content.Intent.ACTION_OPEN_DOCUMENT_TREE
|
import android.content.Intent.ACTION_OPEN_DOCUMENT_TREE
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import com.dropbox.core.android.Auth
|
|
||||||
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential
|
|
||||||
import com.google.api.services.drive.DriveScopes
|
|
||||||
import com.microsoft.identity.client.AuthenticationCallback
|
|
||||||
import com.microsoft.identity.client.IAccount
|
|
||||||
import com.microsoft.identity.client.IAuthenticationResult
|
|
||||||
import com.microsoft.identity.client.IMultipleAccountPublicClientApplication
|
|
||||||
import com.microsoft.identity.client.IPublicClientApplication
|
|
||||||
import com.microsoft.identity.client.PublicClientApplication
|
|
||||||
import com.microsoft.identity.client.exception.MsalClientException
|
|
||||||
import com.microsoft.identity.client.exception.MsalException
|
|
||||||
import com.microsoft.identity.client.exception.MsalServiceException
|
|
||||||
import com.microsoft.identity.client.exception.MsalUiRequiredException
|
|
||||||
import org.cryptomator.data.util.X509CertificateHelper
|
import org.cryptomator.data.util.X509CertificateHelper
|
||||||
import org.cryptomator.domain.Cloud
|
import org.cryptomator.domain.Cloud
|
||||||
import org.cryptomator.domain.CloudType
|
import org.cryptomator.domain.CloudType
|
||||||
@ -39,7 +26,6 @@ import org.cryptomator.domain.usecases.cloud.AddOrChangeCloudConnectionUseCase
|
|||||||
import org.cryptomator.domain.usecases.cloud.GetCloudsUseCase
|
import org.cryptomator.domain.usecases.cloud.GetCloudsUseCase
|
||||||
import org.cryptomator.domain.usecases.cloud.GetUsernameUseCase
|
import org.cryptomator.domain.usecases.cloud.GetUsernameUseCase
|
||||||
import org.cryptomator.generator.Callback
|
import org.cryptomator.generator.Callback
|
||||||
import org.cryptomator.presentation.BuildConfig
|
|
||||||
import org.cryptomator.presentation.R
|
import org.cryptomator.presentation.R
|
||||||
import org.cryptomator.presentation.exception.ExceptionHandlers
|
import org.cryptomator.presentation.exception.ExceptionHandlers
|
||||||
import org.cryptomator.presentation.intent.AuthenticateCloudIntent
|
import org.cryptomator.presentation.intent.AuthenticateCloudIntent
|
||||||
@ -175,12 +161,12 @@ class AuthenticateCloudPresenter @Inject constructor( //
|
|||||||
private fun startAuthentication() {
|
private fun startAuthentication() {
|
||||||
showProgress(ProgressModel(ProgressStateModel.AUTHENTICATION))
|
showProgress(ProgressModel(ProgressStateModel.AUTHENTICATION))
|
||||||
authenticationStarted = true
|
authenticationStarted = true
|
||||||
Auth.startOAuth2Authentication(context(), BuildConfig.DROPBOX_API_KEY)
|
DropboxAuthHelper.startOAuth2Authentication(context())
|
||||||
view?.skipTransition()
|
view?.skipTransition()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleAuthenticationResult(cloudModel: CloudModel) {
|
private fun handleAuthenticationResult(cloudModel: CloudModel) {
|
||||||
val authToken = Auth.getOAuth2Token()
|
val authToken = DropboxAuthHelper.getOAuth2Token()
|
||||||
if (authToken == null) {
|
if (authToken == null) {
|
||||||
failAuthentication(cloudModel.name())
|
failAuthentication(cloudModel.name())
|
||||||
} else {
|
} else {
|
||||||
@ -221,11 +207,10 @@ class AuthenticateCloudPresenter @Inject constructor( //
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun chooseAccount(cloud: CloudModel) {
|
private fun chooseAccount(cloud: CloudModel) {
|
||||||
val chooseAccountIntent = GoogleAccountCredential.usingOAuth2(context(), setOf(DriveScopes.DRIVE)).newChooseAccountIntent()
|
|
||||||
try {
|
try {
|
||||||
requestActivityResult( //
|
requestActivityResult( //
|
||||||
ActivityResultCallbacks.onGoogleDriveAuthenticated(cloud), //
|
ActivityResultCallbacks.onGoogleDriveAuthenticated(cloud), //
|
||||||
chooseAccountIntent
|
GoogleAuthHelper.getChooseAccountIntent(context())
|
||||||
)
|
)
|
||||||
} catch (e: ActivityNotFoundException) {
|
} catch (e: ActivityNotFoundException) {
|
||||||
view?.showMessage(R.string.error_play_services_not_available)
|
view?.showMessage(R.string.error_play_services_not_available)
|
||||||
@ -261,6 +246,7 @@ class AuthenticateCloudPresenter @Inject constructor( //
|
|||||||
private inner class OnedriveAuthStrategy : AuthStrategy {
|
private inner class OnedriveAuthStrategy : AuthStrategy {
|
||||||
|
|
||||||
private var authenticationStarted = false
|
private var authenticationStarted = false
|
||||||
|
|
||||||
override fun supports(cloud: CloudModel): Boolean {
|
override fun supports(cloud: CloudModel): Boolean {
|
||||||
return cloud.cloudType() == CloudTypeModel.ONEDRIVE
|
return cloud.cloudType() == CloudTypeModel.ONEDRIVE
|
||||||
}
|
}
|
||||||
@ -276,107 +262,11 @@ class AuthenticateCloudPresenter @Inject constructor( //
|
|||||||
|
|
||||||
Toast.makeText(context(), R.string.notification_authenticating, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context(), R.string.notification_authenticating, Toast.LENGTH_SHORT).show()
|
||||||
|
|
||||||
PublicClientApplication.createMultipleAccountPublicClientApplication(
|
OnedriveAuthentication.refreshOrCheckAuth(activity(), cloud.toCloud() as OnedriveCloud, { authenticatedCloud ->
|
||||||
context(),
|
getUsernameAndSuceedAuthentication(authenticatedCloud)
|
||||||
R.raw.auth_config_onedrive,
|
}, {
|
||||||
object : IPublicClientApplication.IMultipleAccountApplicationCreatedListener {
|
failAuthentication(cloud.name())
|
||||||
override fun onCreated(application: IMultipleAccountPublicClientApplication) {
|
})
|
||||||
application.getAccounts(object : IPublicClientApplication.LoadAccountsCallback {
|
|
||||||
override fun onTaskCompleted(accounts: List<IAccount>) {
|
|
||||||
if (accounts.isEmpty()) {
|
|
||||||
application.acquireToken(activity(), onedriveScopes(), getAuthInteractiveCallback(cloud))
|
|
||||||
} else {
|
|
||||||
accounts.find { account -> account.username == cloud.username() }?.let {
|
|
||||||
application.acquireTokenSilentAsync(
|
|
||||||
onedriveScopes(),
|
|
||||||
it,
|
|
||||||
"https://login.microsoftonline.com/common",
|
|
||||||
getAuthSilentCallback(cloud, application)
|
|
||||||
)
|
|
||||||
} ?: application.acquireToken(activity(), onedriveScopes(), getAuthInteractiveCallback(cloud))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onError(e: MsalException) {
|
|
||||||
Timber.tag("AuthenticateCloudPresenter").e(e, "Error to get accounts")
|
|
||||||
failAuthentication(cloud.name())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onError(e: MsalException) {
|
|
||||||
Timber.tag("AuthenticateCloudPresenter").i(e, "Error in configuration")
|
|
||||||
failAuthentication(cloud.name())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getAuthSilentCallback(cloud: CloudModel, application: IMultipleAccountPublicClientApplication): AuthenticationCallback {
|
|
||||||
return object : AuthenticationCallback {
|
|
||||||
|
|
||||||
override fun onSuccess(authenticationResult: IAuthenticationResult) {
|
|
||||||
Timber.tag("AuthenticateCloudPresenter").i("Successfully authenticated")
|
|
||||||
handleAuthenticationResult(cloud, authenticationResult.accessToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onError(e: MsalException) {
|
|
||||||
Timber.tag("AuthenticateCloudPresenter").e(e, "Failed to acquireToken")
|
|
||||||
when (e) {
|
|
||||||
is MsalClientException -> {
|
|
||||||
/* Exception inside MSAL, more info inside MsalError.java */
|
|
||||||
failAuthentication(cloud.name())
|
|
||||||
}
|
|
||||||
is MsalServiceException -> {
|
|
||||||
/* Exception when communicating with the STS, likely config issue */
|
|
||||||
failAuthentication(cloud.name())
|
|
||||||
}
|
|
||||||
is MsalUiRequiredException -> {
|
|
||||||
/* Tokens expired or no session, retry with interactive */
|
|
||||||
application.acquireToken(activity(), onedriveScopes(), getAuthInteractiveCallback(cloud))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCancel() {
|
|
||||||
Timber.tag("AuthenticateCloudPresenter").i("User cancelled login")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getAuthInteractiveCallback(cloud: CloudModel): AuthenticationCallback {
|
|
||||||
return object : AuthenticationCallback {
|
|
||||||
|
|
||||||
override fun onSuccess(authenticationResult: IAuthenticationResult) {
|
|
||||||
Timber.tag("AuthenticateCloudPresenter").i("Successfully authenticated")
|
|
||||||
handleAuthenticationResult(cloud, authenticationResult.accessToken, authenticationResult.account.username)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onError(e: MsalException) {
|
|
||||||
Timber.tag("AuthenticateCloudPresenter").e(e, "Successfully authenticated")
|
|
||||||
failAuthentication(cloud.name())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCancel() {
|
|
||||||
Timber.tag("AuthenticateCloudPresenter").i("User cancelled login")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleAuthenticationResult(cloud: CloudModel, accessToken: String) {
|
|
||||||
getUsernameAndSuceedAuthentication( //
|
|
||||||
OnedriveCloud.aCopyOf(cloud.toCloud() as OnedriveCloud) //
|
|
||||||
.withAccessToken(encrypt(accessToken)) //
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleAuthenticationResult(cloud: CloudModel, accessToken: String, username: String) {
|
|
||||||
getUsernameAndSuceedAuthentication( //
|
|
||||||
OnedriveCloud.aCopyOf(cloud.toCloud() as OnedriveCloud) //
|
|
||||||
.withAccessToken(encrypt(accessToken)) //
|
|
||||||
.withUsername(username)
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
|||||||
package org.cryptomator.presentation.presenter
|
package org.cryptomator.presentation.presenter
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
import org.cryptomator.domain.Cloud
|
import org.cryptomator.domain.Cloud
|
||||||
import org.cryptomator.domain.di.PerView
|
import org.cryptomator.domain.di.PerView
|
||||||
import org.cryptomator.domain.exception.FatalBackendException
|
import org.cryptomator.domain.exception.FatalBackendException
|
||||||
@ -12,6 +13,7 @@ import org.cryptomator.presentation.intent.Intents
|
|||||||
import org.cryptomator.presentation.model.CloudTypeModel
|
import org.cryptomator.presentation.model.CloudTypeModel
|
||||||
import org.cryptomator.presentation.model.mappers.CloudModelMapper
|
import org.cryptomator.presentation.model.mappers.CloudModelMapper
|
||||||
import org.cryptomator.presentation.ui.activity.view.ChooseCloudServiceView
|
import org.cryptomator.presentation.ui.activity.view.ChooseCloudServiceView
|
||||||
|
import org.cryptomator.presentation.ui.snackbar.SnackbarAction
|
||||||
import org.cryptomator.presentation.workflow.ActivityResult
|
import org.cryptomator.presentation.workflow.ActivityResult
|
||||||
import org.cryptomator.presentation.workflow.AddExistingVaultWorkflow
|
import org.cryptomator.presentation.workflow.AddExistingVaultWorkflow
|
||||||
import org.cryptomator.presentation.workflow.CreateNewVaultWorkflow
|
import org.cryptomator.presentation.workflow.CreateNewVaultWorkflow
|
||||||
@ -37,6 +39,11 @@ class ChooseCloudServicePresenter @Inject constructor( //
|
|||||||
|
|
||||||
if (BuildConfig.FLAVOR == "fdroid") {
|
if (BuildConfig.FLAVOR == "fdroid") {
|
||||||
cloudTypeModels.remove(CloudTypeModel.GOOGLE_DRIVE)
|
cloudTypeModels.remove(CloudTypeModel.GOOGLE_DRIVE)
|
||||||
|
} else if (BuildConfig.FLAVOR == "lite") {
|
||||||
|
cloudTypeModels.remove(CloudTypeModel.GOOGLE_DRIVE)
|
||||||
|
cloudTypeModels.remove(CloudTypeModel.DROPBOX)
|
||||||
|
cloudTypeModels.remove(CloudTypeModel.ONEDRIVE)
|
||||||
|
cloudTypeModels.remove(CloudTypeModel.PCLOUD)
|
||||||
}
|
}
|
||||||
|
|
||||||
view?.render(cloudTypeModels)
|
view?.render(cloudTypeModels)
|
||||||
@ -87,6 +94,18 @@ class ChooseCloudServicePresenter @Inject constructor( //
|
|||||||
finishWithResult(cloudModelMapper.toModel(cloud))
|
finishWithResult(cloudModelMapper.toModel(cloud))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun showCloudMissingSnackbarHintInLiteVariant() {
|
||||||
|
if (BuildConfig.FLAVOR == "lite") {
|
||||||
|
view?.showSnackbar(R.string.snack_bar_cryptomator_variants_hint, object: SnackbarAction {
|
||||||
|
override fun onClick(v: View?) {
|
||||||
|
startIntent(Intents.cryptomatorVariantsIntent())
|
||||||
|
}
|
||||||
|
override val text: Int
|
||||||
|
get() = R.string.snack_bar_cryptomator_variants_title
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
unsubscribeOnDestroy(getCloudsUseCase)
|
unsubscribeOnDestroy(getCloudsUseCase)
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,6 @@ import android.content.ActivityNotFoundException
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import com.microsoft.identity.client.AuthenticationCallback
|
|
||||||
import com.microsoft.identity.client.IAccount
|
|
||||||
import com.microsoft.identity.client.IAuthenticationResult
|
|
||||||
import com.microsoft.identity.client.IMultipleAccountPublicClientApplication
|
|
||||||
import com.microsoft.identity.client.IPublicClientApplication
|
|
||||||
import com.microsoft.identity.client.PublicClientApplication
|
|
||||||
import com.microsoft.identity.client.exception.MsalException
|
|
||||||
import org.cryptomator.domain.Cloud
|
import org.cryptomator.domain.Cloud
|
||||||
import org.cryptomator.domain.LocalStorageCloud
|
import org.cryptomator.domain.LocalStorageCloud
|
||||||
import org.cryptomator.domain.OnedriveCloud
|
import org.cryptomator.domain.OnedriveCloud
|
||||||
@ -137,49 +130,11 @@ class CloudConnectionListPresenter @Inject constructor( //
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun addOnedriveCloud() {
|
private fun addOnedriveCloud() {
|
||||||
PublicClientApplication.createMultipleAccountPublicClientApplication(
|
OnedriveAuthentication.getAuthenticatedOnedriveCloud(activity(), { cloud ->
|
||||||
context(),
|
saveOnedriveCloud(cloud)
|
||||||
R.raw.auth_config_onedrive,
|
}, { e ->
|
||||||
object : IPublicClientApplication.IMultipleAccountApplicationCreatedListener {
|
showError(e)
|
||||||
override fun onCreated(application: IMultipleAccountPublicClientApplication) {
|
})
|
||||||
application.getAccounts(object : IPublicClientApplication.LoadAccountsCallback {
|
|
||||||
override fun onTaskCompleted(accounts: List<IAccount>) {
|
|
||||||
application.acquireToken(activity(), AuthenticateCloudPresenter.onedriveScopes(), getAuthInteractiveCallback())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onError(e: MsalException) {
|
|
||||||
Timber.tag("AuthenticateCloudPresenter").e(e, "Error to get accounts")
|
|
||||||
showError(e);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onError(e: MsalException) {
|
|
||||||
Timber.tag("AuthenticateCloudPresenter").i(e, "Error in configuration")
|
|
||||||
showError(e);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getAuthInteractiveCallback(): AuthenticationCallback {
|
|
||||||
return object : AuthenticationCallback {
|
|
||||||
|
|
||||||
override fun onSuccess(authenticationResult: IAuthenticationResult) {
|
|
||||||
Timber.tag("AuthenticateCloudPresenter").i("Successfully authenticated")
|
|
||||||
val accessToken = CredentialCryptor.getInstance(context()).encrypt(authenticationResult.accessToken)
|
|
||||||
val onedriveSkeleton = OnedriveCloud.aOnedriveCloud().withAccessToken(accessToken).withUsername(authenticationResult.account.username).build()
|
|
||||||
saveOnedriveCloud(onedriveSkeleton)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onError(e: MsalException) {
|
|
||||||
Timber.tag("AuthenticateCloudPresenter").e(e, "Successfully authenticated")
|
|
||||||
showError(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCancel() {
|
|
||||||
Timber.tag("AuthenticateCloudPresenter").i("User cancelled login")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveOnedriveCloud(onedriveSkeleton: OnedriveCloud) {
|
private fun saveOnedriveCloud(onedriveSkeleton: OnedriveCloud) {
|
||||||
|
@ -140,9 +140,28 @@ class CloudSettingsPresenter @Inject constructor( //
|
|||||||
it.add(aS3Cloud())
|
it.add(aS3Cloud())
|
||||||
it.add(aLocalCloud())
|
it.add(aLocalCloud())
|
||||||
}
|
}
|
||||||
|
.filter { cloud -> !(BuildConfig.FLAVOR == "lite" && excludeApiCloudsInLite(cloud.cloudType())) } //
|
||||||
view?.render(cloudModel)
|
view?.render(cloudModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun excludeApiCloudsInLite(cloudType: CloudTypeModel): Boolean {
|
||||||
|
return when (cloudType) {
|
||||||
|
CloudTypeModel.GOOGLE_DRIVE -> {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
CloudTypeModel.ONEDRIVE -> {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
CloudTypeModel.DROPBOX -> {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
CloudTypeModel.PCLOUD -> {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun aOnedriveCloud(): OnedriveCloudModel {
|
private fun aOnedriveCloud(): OnedriveCloudModel {
|
||||||
return OnedriveCloudModel(OnedriveCloud.aOnedriveCloud().build())
|
return OnedriveCloudModel(OnedriveCloud.aOnedriveCloud().build())
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,103 @@
|
|||||||
|
package org.cryptomator.presentation.presenter
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.widget.Toast
|
||||||
|
import com.google.common.base.Optional
|
||||||
|
import org.cryptomator.data.util.NetworkConnectionCheck
|
||||||
|
import org.cryptomator.domain.di.PerView
|
||||||
|
import org.cryptomator.domain.usecases.DoUpdateCheckUseCase
|
||||||
|
import org.cryptomator.domain.usecases.DoUpdateUseCase
|
||||||
|
import org.cryptomator.domain.usecases.NoOpResultHandler
|
||||||
|
import org.cryptomator.domain.usecases.UpdateCheck
|
||||||
|
import org.cryptomator.presentation.R
|
||||||
|
import org.cryptomator.presentation.exception.ExceptionHandlers
|
||||||
|
import org.cryptomator.presentation.model.ProgressModel
|
||||||
|
import org.cryptomator.presentation.ui.activity.view.CryptomatorVariantsView
|
||||||
|
import org.cryptomator.presentation.util.FileUtil
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@PerView
|
||||||
|
class CryptomatorVariantsPresenter @Inject constructor(
|
||||||
|
//
|
||||||
|
exceptionMappings: ExceptionHandlers, //
|
||||||
|
private val updateCheckUseCase: DoUpdateCheckUseCase, //
|
||||||
|
private val updateUseCase: DoUpdateUseCase, //
|
||||||
|
private val networkConnectionCheck: NetworkConnectionCheck, //
|
||||||
|
private val fileUtil: FileUtil, //
|
||||||
|
) : Presenter<CryptomatorVariantsView>(exceptionMappings) {
|
||||||
|
|
||||||
|
private val fDroidPackageName = "org.fdroid.fdroid"
|
||||||
|
|
||||||
|
fun onInstallMainFDroidVariantClicked() {
|
||||||
|
context().packageManager.getLaunchIntentForPackage(fDroidPackageName)?.let {
|
||||||
|
it.data = Uri.parse("https://f-droid.org/packages/org.cryptomator.light")
|
||||||
|
context().startActivity(it)
|
||||||
|
} ?: Toast.makeText(context(), R.string.error_interact_with_fdroid_but_fdroid_missing, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onAddRepoClicked() {
|
||||||
|
context().packageManager.getLaunchIntentForPackage(fDroidPackageName)?.let {
|
||||||
|
it.data = Uri.parse("https://static.cryptomator.org/android/fdroid/repo?fingerprint=F7C3EC3B0D588D3CB52983E9EB1A7421C93D4339A286398E71D7B651E8D8ECDD")
|
||||||
|
context().startActivity(it)
|
||||||
|
} ?: Toast.makeText(context(), R.string.error_interact_with_fdroid_but_fdroid_missing, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onInstallFDroidVariantClicked() {
|
||||||
|
context().packageManager.getLaunchIntentForPackage(fDroidPackageName)?.let {
|
||||||
|
it.data = Uri.parse("https://f-droid.org/packages/org.cryptomator")
|
||||||
|
context().startActivity(it)
|
||||||
|
} ?: Toast.makeText(context(), R.string.error_interact_with_fdroid_but_fdroid_missing, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onInstallWebsiteVariantClicked() {
|
||||||
|
if (networkConnectionCheck.isPresent) {
|
||||||
|
view?.showProgress(ProgressModel.GENERIC)
|
||||||
|
|
||||||
|
updateCheckUseCase //
|
||||||
|
.withVersion("0.0.0")
|
||||||
|
.run(object : NoOpResultHandler<Optional<UpdateCheck>>() {
|
||||||
|
override fun onSuccess(result: Optional<UpdateCheck>) {
|
||||||
|
installUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(e: Throwable) {
|
||||||
|
view?.showProgress(ProgressModel.COMPLETED)
|
||||||
|
showError(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Toast.makeText(context(), R.string.error_update_no_internet, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun installUpdate() {
|
||||||
|
val uri = fileUtil.contentUriForNewTempFile("cryptomator.apk")
|
||||||
|
val file = fileUtil.tempFile("cryptomator.apk")
|
||||||
|
|
||||||
|
updateUseCase //
|
||||||
|
.withFile(file) //
|
||||||
|
.run(object : NoOpResultHandler<Void?>() {
|
||||||
|
override fun onError(e: Throwable) {
|
||||||
|
showError(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSuccess(result: Void?) {
|
||||||
|
super.onSuccess(result)
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW)
|
||||||
|
intent.setDataAndType(uri, "application/vnd.android.package-archive")
|
||||||
|
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
context().startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFinished() {
|
||||||
|
view?.showProgress(ProgressModel.COMPLETED)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
unsubscribeOnDestroy(updateCheckUseCase, updateUseCase)
|
||||||
|
}
|
||||||
|
}
|
@ -83,6 +83,9 @@ class SettingsPresenter @Inject internal constructor(
|
|||||||
"fdroid" -> {
|
"fdroid" -> {
|
||||||
"F-Droid"
|
"F-Droid"
|
||||||
}
|
}
|
||||||
|
"lite" -> {
|
||||||
|
"F-Droid Main Repo Edition"
|
||||||
|
}
|
||||||
else -> "Google Play"
|
else -> "Google Play"
|
||||||
}
|
}
|
||||||
return StringBuilder().append("## ").append(context().getString(R.string.error_report_subject)).append("\n\n") //
|
return StringBuilder().append("## ").append(context().getString(R.string.error_report_subject)).append("\n\n") //
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
package org.cryptomator.presentation.presenter
|
|
||||||
|
|
||||||
import org.cryptomator.domain.di.PerView
|
|
||||||
import org.cryptomator.presentation.exception.ExceptionHandlers
|
|
||||||
import org.cryptomator.presentation.intent.Intents
|
|
||||||
import org.cryptomator.presentation.ui.activity.view.SplashView
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@PerView
|
|
||||||
class SplashPresenter @Inject constructor(exceptionMappings: ExceptionHandlers) : Presenter<SplashView>(exceptionMappings) {
|
|
||||||
|
|
||||||
override fun resumed() {
|
|
||||||
Intents.vaultListIntent().startActivity(this)
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
}
|
|