mirror of
https://github.com/TeamAmaze/AmazeFileManager.git
synced 2026-10-17 05:08:00 +08:00
Add SAF permission grant routine for stock installs
Fixes #2592. - Refactored FtpServerFragment to allow different ActivityResultLauncher instances of different callbacks to be created in same code - Added routine to prompt user to grant access to device's internal storage (default FTP server shared folder) at SAF level
This commit is contained in:
@@ -35,6 +35,7 @@ import android.os.Build.VERSION_CODES.M
|
||||
import android.os.Environment
|
||||
import android.os.IBinder
|
||||
import android.os.SystemClock
|
||||
import android.provider.DocumentsContract
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.amaze.filemanager.R
|
||||
import com.amaze.filemanager.application.AppConfig
|
||||
@@ -242,7 +243,10 @@ class FtpService : Service(), Runnable {
|
||||
val DEFAULT_PATH: String = if (Build.VERSION.SDK_INT < M) {
|
||||
Environment.getExternalStorageDirectory().absolutePath
|
||||
} else {
|
||||
"content://com.android.externalstorage.documents/tree/primary%3A"
|
||||
DocumentsContract.buildTreeDocumentUri(
|
||||
"com.android.externalstorage.documents",
|
||||
"primary:"
|
||||
).toString()
|
||||
}
|
||||
|
||||
// RequestStartStopReceiver listens for these actions to start/stop this server
|
||||
|
||||
@@ -25,19 +25,23 @@ import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Build.VERSION_CODES.LOLLIPOP
|
||||
import android.os.Build.VERSION_CODES.M
|
||||
import android.os.Build.VERSION_CODES.*
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.os.Process
|
||||
import android.provider.DocumentsContract
|
||||
import android.provider.DocumentsContract.EXTRA_INITIAL_URI
|
||||
import android.provider.Settings
|
||||
import android.text.InputType
|
||||
import android.text.Spanned
|
||||
import android.view.*
|
||||
import android.widget.*
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.text.HtmlCompat
|
||||
@@ -50,6 +54,8 @@ import com.afollestad.materialdialogs.folderselector.FolderChooserDialog
|
||||
import com.amaze.filemanager.R
|
||||
import com.amaze.filemanager.application.AppConfig
|
||||
import com.amaze.filemanager.asynchronous.services.ftp.FtpService
|
||||
import com.amaze.filemanager.asynchronous.services.ftp.FtpService.Companion.DEFAULT_PATH
|
||||
import com.amaze.filemanager.asynchronous.services.ftp.FtpService.Companion.KEY_PREFERENCE_PATH
|
||||
import com.amaze.filemanager.asynchronous.services.ftp.FtpService.Companion.getLocalInetAddress
|
||||
import com.amaze.filemanager.asynchronous.services.ftp.FtpService.Companion.isConnectedToLocalNetwork
|
||||
import com.amaze.filemanager.asynchronous.services.ftp.FtpService.Companion.isConnectedToWifi
|
||||
@@ -101,22 +107,17 @@ class FtpServerFragment : Fragment(R.layout.fragment_ftp) {
|
||||
|
||||
private val mainActivity: MainActivity get() = requireActivity() as MainActivity
|
||||
|
||||
@Suppress("LabeledExpression")
|
||||
private val activityResultHandler = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) {
|
||||
if (it.resultCode == RESULT_OK && Build.VERSION.SDK_INT >= LOLLIPOP) {
|
||||
val directoryUri = it.data?.data ?: return@registerForActivityResult
|
||||
requireContext().contentResolver.takePersistableUriPermission(
|
||||
directoryUri,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
changeFTPServerPath(directoryUri.toString())
|
||||
updatePathText()
|
||||
}
|
||||
private val activityResultHandlerOnFtpServerPathUpdate = createOpenDocumentTreeIntentCallback {
|
||||
directoryUri ->
|
||||
changeFTPServerPath(directoryUri.toString())
|
||||
updatePathText()
|
||||
}
|
||||
|
||||
private val activityResultHandlerOnFtpServerPathGrantedSafAccess =
|
||||
createOpenDocumentTreeIntentCallback {
|
||||
doStartServer()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
@@ -171,7 +172,7 @@ class FtpServerFragment : Fragment(R.layout.fragment_ftp) {
|
||||
true
|
||||
) { _: MaterialDialog?, _: CharSequence? -> }
|
||||
.inputType(InputType.TYPE_CLASS_NUMBER)
|
||||
.onPositive { dialog: MaterialDialog, which: DialogAction? ->
|
||||
.onPositive { dialog: MaterialDialog, _: DialogAction? ->
|
||||
val editText = dialog.inputEditText
|
||||
if (editText != null) {
|
||||
val name = editText.text.toString()
|
||||
@@ -200,7 +201,9 @@ class FtpServerFragment : Fragment(R.layout.fragment_ftp) {
|
||||
}
|
||||
R.id.ftp_path -> {
|
||||
if (Build.VERSION.SDK_INT >= M) {
|
||||
activityResultHandler.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE))
|
||||
activityResultHandlerOnFtpServerPathUpdate.launch(
|
||||
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
)
|
||||
} else {
|
||||
val dialogBuilder = FolderChooserDialog.Builder(requireActivity())
|
||||
dialogBuilder
|
||||
@@ -220,7 +223,7 @@ class FtpServerFragment : Fragment(R.layout.fragment_ftp) {
|
||||
val loginDialogView =
|
||||
DialogFtpLoginBinding.inflate(LayoutInflater.from(requireContext())).apply {
|
||||
initLoginDialogViews(this)
|
||||
loginDialogBuilder.onPositive { dialog: MaterialDialog, _: DialogAction ->
|
||||
loginDialogBuilder.onPositive { _: MaterialDialog, _: DialogAction ->
|
||||
if (checkboxFtpAnonymous.isChecked) {
|
||||
// remove preferences
|
||||
setFTPUsername("")
|
||||
@@ -364,12 +367,75 @@ class FtpServerFragment : Fragment(R.layout.fragment_ftp) {
|
||||
updateStatus()
|
||||
}
|
||||
|
||||
@Suppress("LabeledExpression")
|
||||
private fun createOpenDocumentTreeIntentCallback(callback: (directoryUri: Uri) -> Unit):
|
||||
ActivityResultLauncher<Intent> {
|
||||
return registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) {
|
||||
if (it.resultCode == RESULT_OK && Build.VERSION.SDK_INT >= LOLLIPOP) {
|
||||
val directoryUri = it.data?.data ?: return@registerForActivityResult
|
||||
requireContext().contentResolver.takePersistableUriPermission(
|
||||
directoryUri, GRANT_URI_RW_PERMISSION
|
||||
)
|
||||
callback.invoke(directoryUri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Check URI access if */
|
||||
private fun checkUriAccessIfNecessary(callback: () -> Unit) {
|
||||
if (Build.VERSION.SDK_INT >= M) {
|
||||
val directoryUri = mainActivity.prefs.getString(KEY_PREFERENCE_PATH, DEFAULT_PATH)
|
||||
Uri.parse(directoryUri).run {
|
||||
if (requireContext().checkUriPermission(
|
||||
this, Process.myPid(), Process.myUid(),
|
||||
GRANT_URI_RW_PERMISSION
|
||||
) == PackageManager.PERMISSION_DENIED
|
||||
) {
|
||||
mainActivity.accent.run {
|
||||
MaterialDialog.Builder(mainActivity)
|
||||
.content(R.string.ftp_prompt_accept_first_start_saf_access)
|
||||
.widgetColor(accentColor)
|
||||
.theme(mainActivity.appTheme.materialDialogTheme)
|
||||
.title(R.string.ftp_prompt_accept_first_start_saf_access_title)
|
||||
.positiveText(R.string.ok)
|
||||
.positiveColor(accentColor)
|
||||
.negativeText(R.string.cancel)
|
||||
.negativeColor(accentColor)
|
||||
.onPositive { dialog, _ ->
|
||||
activityResultHandlerOnFtpServerPathGrantedSafAccess.launch(
|
||||
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).also {
|
||||
if (Build.VERSION.SDK_INT >= O &&
|
||||
directoryUri.equals(DEFAULT_PATH)
|
||||
) {
|
||||
it.putExtra(
|
||||
EXTRA_INITIAL_URI,
|
||||
DocumentsContract.buildDocumentUri(
|
||||
"com.android.externalstorage.documents",
|
||||
"primary"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
dialog.dismiss()
|
||||
}.build().show()
|
||||
}
|
||||
} else {
|
||||
callback.invoke()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
callback.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
/** Sends a broadcast to start ftp server */
|
||||
private fun startServer() {
|
||||
requireContext().sendBroadcast(
|
||||
Intent(FtpService.ACTION_START_FTPSERVER)
|
||||
.setPackage(requireContext().packageName)
|
||||
)
|
||||
checkUriAccessIfNecessary {
|
||||
doStartServer()
|
||||
}
|
||||
}
|
||||
|
||||
/** Sends a broadcast to stop ftp server */
|
||||
@@ -380,6 +446,11 @@ class FtpServerFragment : Fragment(R.layout.fragment_ftp) {
|
||||
)
|
||||
}
|
||||
|
||||
private fun doStartServer() = requireContext().sendBroadcast(
|
||||
Intent(FtpService.ACTION_START_FTPSERVER)
|
||||
.setPackage(requireContext().packageName)
|
||||
)
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
val wifiFilter = IntentFilter()
|
||||
@@ -433,7 +504,7 @@ class FtpServerFragment : Fragment(R.layout.fragment_ftp) {
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
ftpPasswordVisibleButton.setOnClickListener { v: View? ->
|
||||
ftpPasswordVisibleButton.setOnClickListener {
|
||||
if (password.text.toString().contains("\u25CF")) {
|
||||
// password was not visible, let's make it visible
|
||||
password.text = resources.getString(R.string.password) + ": " + passwordDecrypted
|
||||
@@ -728,5 +799,7 @@ class FtpServerFragment : Fragment(R.layout.fragment_ftp) {
|
||||
companion object {
|
||||
const val TAG = "FtpServerFragment"
|
||||
const val REQUEST_CODE_SAF_FTP = 225
|
||||
const val GRANT_URI_RW_PERMISSION =
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
}
|
||||
}
|
||||
|
||||
@@ -707,5 +707,9 @@
|
||||
<string name="disable">Disable</string>
|
||||
<string name="choose_operation">Choose operation to perform</string>
|
||||
<string name="remember">Remember for next time</string>
|
||||
<string name="ftp_prompt_accept_first_start_saf_access_title">Grant SAF access for FTP server</string>
|
||||
<string name="ftp_prompt_accept_first_start_saf_access">Since this is your first run of the FTP server, and you are using the device\'s internal storage as shared folder, please allow Amaze to access the storage using SAF.\n\n
|
||||
|
||||
You only need to do this once, until the next time you select a new location for sharing.</string>
|
||||
</resources>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user