Allow to setup NextCloud account during restore
This is especially useful when restore is only allowed during SetupWizard and the backup was stored on a NextCloud account.
This commit is contained in:
parent
2ebc7c9479
commit
791f68300d
8 changed files with 1345 additions and 9 deletions
app/src/main
java/com/stevesoltys/seedvault/ui/storage
res
|
@ -3,6 +3,7 @@ package com.stevesoltys.seedvault.ui.storage
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
import com.stevesoltys.seedvault.transport.backup.plugins.DIRECTORY_ROOT
|
import com.stevesoltys.seedvault.transport.backup.plugins.DIRECTORY_ROOT
|
||||||
|
@ -14,19 +15,19 @@ internal class RestoreStorageViewModel(private val app: Application) : StorageVi
|
||||||
|
|
||||||
override val isRestoreOperation = true
|
override val isRestoreOperation = true
|
||||||
|
|
||||||
override fun onLocationSet(uri: Uri) {
|
override fun onLocationSet(uri: Uri) = Thread {
|
||||||
if (hasBackup(uri)) {
|
if (hasBackup(uri)) {
|
||||||
saveStorage(uri)
|
saveStorage(uri)
|
||||||
|
|
||||||
mLocationChecked.setEvent(LocationResult())
|
mLocationChecked.postEvent(LocationResult())
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "Location was rejected: $uri")
|
Log.w(TAG, "Location was rejected: $uri")
|
||||||
|
|
||||||
// notify the UI that the location was invalid
|
// notify the UI that the location was invalid
|
||||||
val errorMsg = app.getString(R.string.restore_invalid_location_message, DIRECTORY_ROOT)
|
val errorMsg = app.getString(R.string.restore_invalid_location_message, DIRECTORY_ROOT)
|
||||||
mLocationChecked.setEvent(LocationResult(errorMsg))
|
mLocationChecked.postEvent(LocationResult(errorMsg))
|
||||||
}
|
}
|
||||||
}
|
}.start()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Searches if there's really a backup available in the given location.
|
* Searches if there's really a backup available in the given location.
|
||||||
|
@ -37,7 +38,11 @@ internal class RestoreStorageViewModel(private val app: Application) : StorageVi
|
||||||
*
|
*
|
||||||
* TODO maybe move this to the RestoreCoordinator once we can inject it
|
* TODO maybe move this to the RestoreCoordinator once we can inject it
|
||||||
*/
|
*/
|
||||||
|
@WorkerThread
|
||||||
private fun hasBackup(folderUri: Uri): Boolean {
|
private fun hasBackup(folderUri: Uri): Boolean {
|
||||||
|
// FIXME This currently fails for NextCloud's DocumentsProvider,
|
||||||
|
// if called right after setting up an account.
|
||||||
|
// It requires three attempts to finally find existing backups.
|
||||||
val parent = DocumentFile.fromTreeUri(app, folderUri) ?: throw AssertionError()
|
val parent = DocumentFile.fromTreeUri(app, folderUri) ?: throw AssertionError()
|
||||||
val rootDir = parent.findFile(DIRECTORY_ROOT) ?: return false
|
val rootDir = parent.findFile(DIRECTORY_ROOT) ?: return false
|
||||||
val backupSets = DocumentsProviderRestorePlugin.getBackups(rootDir)
|
val backupSets = DocumentsProviderRestorePlugin.getBackups(rootDir)
|
||||||
|
|
|
@ -70,7 +70,9 @@ internal class StorageRootAdapter(
|
||||||
else -> summaryView.visibility = GONE
|
else -> summaryView.visibility = GONE
|
||||||
}
|
}
|
||||||
v.setOnClickListener {
|
v.setOnClickListener {
|
||||||
if (!isRestore && item.isInternal()) {
|
if (item.overrideClickListener != null) {
|
||||||
|
item.overrideClickListener.invoke()
|
||||||
|
} else if (!isRestore && item.isInternal()) {
|
||||||
showWarningDialog(v.context, item)
|
showWarningDialog(v.context, item)
|
||||||
} else {
|
} else {
|
||||||
listener.onClick(item)
|
listener.onClick(item)
|
||||||
|
|
|
@ -3,6 +3,8 @@ package com.stevesoltys.seedvault.ui.storage
|
||||||
import android.Manifest.permission.MANAGE_DOCUMENTS
|
import android.Manifest.permission.MANAGE_DOCUMENTS
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.Intent.ACTION_VIEW
|
||||||
|
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
import android.content.pm.PackageManager.GET_META_DATA
|
import android.content.pm.PackageManager.GET_META_DATA
|
||||||
import android.content.pm.ProviderInfo
|
import android.content.pm.ProviderInfo
|
||||||
import android.database.ContentObserver
|
import android.database.ContentObserver
|
||||||
|
@ -24,6 +26,10 @@ const val ROOT_ID_DEVICE = "primary"
|
||||||
const val ROOT_ID_HOME = "home"
|
const val ROOT_ID_HOME = "home"
|
||||||
|
|
||||||
const val AUTHORITY_DOWNLOADS = "com.android.providers.downloads.documents"
|
const val AUTHORITY_DOWNLOADS = "com.android.providers.downloads.documents"
|
||||||
|
const val AUTHORITY_NEXTCLOUD = "org.nextcloud.documents"
|
||||||
|
|
||||||
|
private const val NEXTCLOUD_PACKAGE = "com.nextcloud.client"
|
||||||
|
private const val NEXTCLOUD_ACTIVITY = "com.owncloud.android.authentication.AuthenticatorActivity"
|
||||||
|
|
||||||
data class StorageRoot(
|
data class StorageRoot(
|
||||||
internal val authority: String,
|
internal val authority: String,
|
||||||
|
@ -34,7 +40,8 @@ data class StorageRoot(
|
||||||
internal val summary: String?,
|
internal val summary: String?,
|
||||||
internal val availableBytes: Long?,
|
internal val availableBytes: Long?,
|
||||||
internal val isUsb: Boolean,
|
internal val isUsb: Boolean,
|
||||||
internal val enabled: Boolean = true) {
|
internal val enabled: Boolean = true,
|
||||||
|
internal val overrideClickListener: (() -> Unit)? = null) {
|
||||||
|
|
||||||
internal val uri: Uri by lazy {
|
internal val uri: Uri by lazy {
|
||||||
DocumentsContract.buildTreeDocumentUri(authority, documentId)
|
DocumentsContract.buildTreeDocumentUri(authority, documentId)
|
||||||
|
@ -86,7 +93,8 @@ internal class StorageRootFetcher(private val context: Context, private val isRe
|
||||||
roots.addAll(getRoots(providerInfo))
|
roots.addAll(getRoots(providerInfo))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isAuthoritySupported(AUTHORITY_STORAGE)) checkOrAddUsbRoot(roots)
|
checkOrAddUsbRoot(roots)
|
||||||
|
checkOrAddNextCloudRoot(roots)
|
||||||
return roots
|
return roots
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +144,10 @@ internal class StorageRootFetcher(private val context: Context, private val isRe
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkOrAddUsbRoot(roots: ArrayList<StorageRoot>) {
|
private fun checkOrAddUsbRoot(roots: ArrayList<StorageRoot>) {
|
||||||
|
if (!isAuthoritySupported(AUTHORITY_STORAGE)) return
|
||||||
|
|
||||||
for (root in roots) {
|
for (root in roots) {
|
||||||
|
// return if we already have a USB storage root
|
||||||
if (root.authority == AUTHORITY_STORAGE && root.isUsb) return
|
if (root.authority == AUTHORITY_STORAGE && root.isUsb) return
|
||||||
}
|
}
|
||||||
val root = StorageRoot(
|
val root = StorageRoot(
|
||||||
|
@ -153,6 +164,44 @@ internal class StorageRootFetcher(private val context: Context, private val isRe
|
||||||
roots.add(root)
|
roots.add(root)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun checkOrAddNextCloudRoot(roots: ArrayList<StorageRoot>) {
|
||||||
|
if (!isRestore) return
|
||||||
|
|
||||||
|
for (root in roots) {
|
||||||
|
// return if we already have a NextCloud storage root
|
||||||
|
if (root.authority == AUTHORITY_NEXTCLOUD) return
|
||||||
|
}
|
||||||
|
val intent = Intent().apply {
|
||||||
|
addFlags(FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
setClassName(NEXTCLOUD_PACKAGE, NEXTCLOUD_ACTIVITY)
|
||||||
|
// setting a nc:// Uri prevents FirstRunActivity to show
|
||||||
|
data = Uri.parse("nc://login/server:")
|
||||||
|
putExtra("onlyAdd", true)
|
||||||
|
}
|
||||||
|
val isInstalled = packageManager.resolveActivity(intent, 0) != null
|
||||||
|
val root = StorageRoot(
|
||||||
|
authority = AUTHORITY_NEXTCLOUD,
|
||||||
|
rootId = "fake",
|
||||||
|
documentId = "fake",
|
||||||
|
icon = getIcon(context, AUTHORITY_NEXTCLOUD, "fake", 0),
|
||||||
|
title = context.getString(R.string.storage_fake_nextcloud_title),
|
||||||
|
summary = context.getString(if (isInstalled) R.string.storage_fake_nextcloud_summary_installed else R.string.storage_fake_nextcloud_summary),
|
||||||
|
availableBytes = null,
|
||||||
|
isUsb = false,
|
||||||
|
enabled = true,
|
||||||
|
overrideClickListener = {
|
||||||
|
if (isInstalled) context.startActivity(intent)
|
||||||
|
else {
|
||||||
|
val uri = Uri.parse("market://details?id=$NEXTCLOUD_PACKAGE")
|
||||||
|
val i = Intent(ACTION_VIEW, uri)
|
||||||
|
i.addFlags(FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
context.startActivity(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
roots.add(root)
|
||||||
|
}
|
||||||
|
|
||||||
private fun ProviderInfo.isSupported(): Boolean {
|
private fun ProviderInfo.isSupported(): Boolean {
|
||||||
return if (!exported) {
|
return if (!exported) {
|
||||||
Log.w(TAG, "Provider is not exported")
|
Log.w(TAG, "Provider is not exported")
|
||||||
|
@ -202,6 +251,7 @@ internal class StorageRootFetcher(private val context: Context, private val isRe
|
||||||
return getPackageIcon(context, authority, icon) ?: when {
|
return getPackageIcon(context, authority, icon) ?: when {
|
||||||
authority == AUTHORITY_STORAGE && rootId == ROOT_ID_DEVICE -> context.getDrawable(R.drawable.ic_phone_android)
|
authority == AUTHORITY_STORAGE && rootId == ROOT_ID_DEVICE -> context.getDrawable(R.drawable.ic_phone_android)
|
||||||
authority == AUTHORITY_STORAGE && rootId != ROOT_ID_HOME -> context.getDrawable(R.drawable.ic_usb)
|
authority == AUTHORITY_STORAGE && rootId != ROOT_ID_HOME -> context.getDrawable(R.drawable.ic_usb)
|
||||||
|
authority == AUTHORITY_NEXTCLOUD -> context.getDrawable(R.drawable.nextcloud)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,6 @@ import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_RESTORE
|
||||||
import com.stevesoltys.seedvault.ui.REQUEST_CODE_OPEN_DOCUMENT_TREE
|
import com.stevesoltys.seedvault.ui.REQUEST_CODE_OPEN_DOCUMENT_TREE
|
||||||
import kotlinx.android.synthetic.main.fragment_storage_root.*
|
import kotlinx.android.synthetic.main.fragment_storage_root.*
|
||||||
|
|
||||||
private val TAG = StorageRootsFragment::class.java.simpleName
|
|
||||||
|
|
||||||
internal class StorageRootsFragment : Fragment(), StorageRootClickedListener {
|
internal class StorageRootsFragment : Fragment(), StorageRootClickedListener {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
24
app/src/main/res/drawable/nextcloud.xml
Normal file
24
app/src/main/res/drawable/nextcloud.xml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Nextcloud Android client application
|
||||||
|
|
||||||
|
Copyright (C) 2017 Andy Scherzinger
|
||||||
|
Copyright (C) 2017 Nextcloud.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 3 of the License, or any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU AFFERO GENERAL PUBLIC LICENSE for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public
|
||||||
|
License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/nextcloud_background"/>
|
||||||
|
<foreground android:drawable="@drawable/nextcloud_foreground"/>
|
||||||
|
</adaptive-icon>
|
1222
app/src/main/res/drawable/nextcloud_background.xml
Normal file
1222
app/src/main/res/drawable/nextcloud_background.xml
Normal file
File diff suppressed because it is too large
Load diff
32
app/src/main/res/drawable/nextcloud_foreground.xml
Normal file
32
app/src/main/res/drawable/nextcloud_foreground.xml
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<!--
|
||||||
|
Nextcloud Android client application
|
||||||
|
|
||||||
|
Copyright (C) 2017 Andy Scherzinger
|
||||||
|
Copyright (C) 2017 Nextcloud.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 3 of the License, or any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU AFFERO GENERAL PUBLIC LICENSE for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public
|
||||||
|
License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="1636.9231"
|
||||||
|
android:viewportHeight="1636.9231">
|
||||||
|
<group android:translateX="286.46155"
|
||||||
|
android:translateY="286.46155">
|
||||||
|
<path
|
||||||
|
android:pathData="M532.7,320C439.3,320 360.9,383.9 337,469.9 316.1,423.9 270.1,391.3 216.6,391.3 143.8,391.3 84,451.2 84,524c-0,72.8 59.8,132.6 132.6,132.7 53.5,-0 99.4,-32.6 120.4,-78.6 23.9,86 102.4,149.9 195.7,149.9 92.8,0 170.8,-63.2 195.3,-148.5 21.2,45.1 66.5,77.2 119.4,77.2 72.8,0 132.7,-59.8 132.6,-132.7 -0,-72.8 -59.9,-132.6 -132.6,-132.6 -52.8,0 -98.2,32 -119.4,77.2 -24.4,-85.3 -102.4,-148.5 -195.3,-148.5zM532.7,397.9c70.1,0 126.1,56 126.1,126.1 0,70.1 -56,126.1 -126.1,126.1 -70.1,-0 -126.1,-56 -126.1,-126.1 0,-70.1 56,-126.1 126.1,-126.1zM216.6,469.2c30.7,0 54.8,24.1 54.8,54.8 0,30.7 -24,54.8 -54.8,54.8 -30.7,0 -54.8,-24.1 -54.8,-54.8 0,-30.7 24.1,-54.8 54.8,-54.8zM847.4,469.2c30.7,-0 54.8,24.1 54.8,54.8 0,30.7 -24.1,54.8 -54.8,54.8 -30.7,0 -54.8,-24.1 -54.8,-54.8 0,-30.7 24.1,-54.8 54.8,-54.8z"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:fillColor="#ffffff"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
|
@ -27,6 +27,9 @@
|
||||||
<string name="storage_fake_drive_title">USB Flash Drive</string>
|
<string name="storage_fake_drive_title">USB Flash Drive</string>
|
||||||
<string name="storage_fake_drive_summary">Needs to be plugged in</string>
|
<string name="storage_fake_drive_summary">Needs to be plugged in</string>
|
||||||
<string name="storage_available_bytes"><xliff:g example="1 GB" id="size">%1$s</xliff:g> free</string>
|
<string name="storage_available_bytes"><xliff:g example="1 GB" id="size">%1$s</xliff:g> free</string>
|
||||||
|
<string name="storage_fake_nextcloud_title">Nextcloud</string>
|
||||||
|
<string name="storage_fake_nextcloud_summary">Click to install</string>
|
||||||
|
<string name="storage_fake_nextcloud_summary_installed">Click to set up account</string>
|
||||||
<string name="storage_check_fragment_backup_title">Initializing backup location…</string>
|
<string name="storage_check_fragment_backup_title">Initializing backup location…</string>
|
||||||
<string name="storage_check_fragment_restore_title">Looking for backups…</string>
|
<string name="storage_check_fragment_restore_title">Looking for backups…</string>
|
||||||
<string name="storage_check_fragment_backup_error">An error occurred while accessing the backup location.</string>
|
<string name="storage_check_fragment_backup_error">An error occurred while accessing the backup location.</string>
|
||||||
|
|
Loading…
Add table
Reference in a new issue