Skip to content

Commit

Permalink
[Feature] Add document manager shortcut and use it for Android/{data,…
Browse files Browse the repository at this point in the history
…obb}.

Fixes: #845
  • Loading branch information
zhanghai committed Aug 20, 2023
1 parent 0efbf83 commit 5bbd452
Show file tree
Hide file tree
Showing 22 changed files with 443 additions and 109 deletions.
10 changes: 10 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,16 @@
android:label="@string/storage_edit_device_storage_title"
android:theme="@style/Theme.MaterialFiles.Translucent" />

<activity
android:name="me.zhanghai.android.files.storage.AddDocumentManagerShortcutActivity"
android:label="@string/storage_add_document_manager_shortcut_title"
android:theme="@style/Theme.MaterialFiles.Translucent" />

<activity
android:name="me.zhanghai.android.files.storage.EditDocumentManagerShortcutDialogActivity"
android:label="@string/storage_edit_document_manager_shortcut_title"
android:theme="@style/Theme.MaterialFiles.Translucent" />

<activity
android:name="me.zhanghai.android.files.storage.AddDocumentTreeActivity"
android:label="@string/storage_add_document_tree_title"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import android.content.ContentResolver
import android.net.Uri
import android.os.Build
import android.provider.DocumentsContract
import me.zhanghai.android.files.app.packageManager

object DocumentsContractCompat {
const val EXTRA_INITIAL_URI = "android.provider.extra.INITIAL_URI"
Expand All @@ -21,6 +22,17 @@ object DocumentsContractCompat {
private const val PATH_CHILDREN = "children"
private const val PATH_TREE = "tree"

/** @see DocumentsContract.PACKAGE_DOCUMENTS_UI */
fun getDocumentsUiPackage(): String? {
// See android.permission.cts.ProviderPermissionTest.testManageDocuments()
val packageInfos = packageManager.getPackagesHoldingPermissions(
arrayOf(android.Manifest.permission.MANAGE_DOCUMENTS), 0
)
val packageInfo = packageInfos.firstOrNull { it.packageName.endsWith(".documentsui") }
?: packageInfos.firstOrNull()
return packageInfo?.packageName
}

/** @see DocumentsContract.isDocumentUri */
fun isDocumentUri(uri: Uri): Boolean {
if (uri.scheme != ContentResolver.SCHEME_CONTENT) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
package me.zhanghai.android.files.file

import android.net.Uri
import android.os.Parcelable
import android.provider.DocumentsContract
import kotlinx.parcelize.Parcelize
import me.zhanghai.android.files.app.contentResolver
import me.zhanghai.android.files.compat.DocumentsContractCompat

@Parcelize
@JvmInline
value class DocumentUri(val value: Uri) {
value class DocumentUri(val value: Uri) : Parcelable {
val documentId: String
get() = DocumentsContract.getDocumentId(value)
}
Expand Down Expand Up @@ -50,6 +53,3 @@ val DocumentUri.displayName: String?
}
return null
}

val DocumentUri.displayNameOrUri: String
get() = displayName ?: value.toString()
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

package me.zhanghai.android.files.navigation

import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
Expand All @@ -14,16 +15,6 @@ import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.LinearLayoutManager
import java8.nio.file.Path
import me.zhanghai.android.files.databinding.NavigationFragmentBinding
import me.zhanghai.android.files.file.DocumentTreeUri
import me.zhanghai.android.files.file.asDocumentTreeUri
import me.zhanghai.android.files.file.releasePersistablePermission
import me.zhanghai.android.files.provider.document.documentTreeUri
import me.zhanghai.android.files.provider.document.isDocumentPath
import me.zhanghai.android.files.settings.StandardDirectoryListActivity
import me.zhanghai.android.files.storage.AddStorageDialogActivity
import me.zhanghai.android.files.storage.Storage
import me.zhanghai.android.files.util.createIntent
import me.zhanghai.android.files.util.putArgs
import me.zhanghai.android.files.util.startActivitySafe

class NavigationFragment : Fragment(), NavigationItem.Listener {
Expand Down Expand Up @@ -77,34 +68,8 @@ class NavigationFragment : Fragment(), NavigationItem.Listener {
listener.navigateToRoot(path)
}

override fun onAddStorage() {
startActivitySafe(AddStorageDialogActivity::class.createIntent())
}

override fun onEditStorage(storage: Storage) {
startActivitySafe(storage.createEditIntent())
}

// TODO
// FIXME: Navigate away on async storage removal
fun removeDocumentTree(treeUri: DocumentTreeUri) {
treeUri.releasePersistablePermission()
val currentPath = listener.currentPath
if (currentPath.isDocumentPath
&& currentPath.documentTreeUri.asDocumentTreeUri() == treeUri) {
listener.navigateToDefaultRoot()
}
}

override fun onEditStandardDirectory(standardDirectory: StandardDirectory) {
startActivitySafe(StandardDirectoryListActivity::class.createIntent())
}

override fun onEditBookmarkDirectory(bookmarkDirectory: BookmarkDirectory) {
startActivitySafe(
EditBookmarkDirectoryDialogActivity::class.createIntent()
.putArgs(EditBookmarkDirectoryDialogFragment.Args(bookmarkDirectory))
)
override fun launchIntent(intent: Intent) {
startActivitySafe(intent)
}

override fun closeNavigationDrawer() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import android.graphics.drawable.Drawable
import androidx.annotation.DrawableRes
import java8.nio.file.Path
import me.zhanghai.android.files.compat.getDrawableCompat
import me.zhanghai.android.files.storage.Storage

abstract class NavigationItem {
abstract val id: Long
Expand All @@ -35,11 +34,7 @@ abstract class NavigationItem {
val currentPath: Path
fun navigateTo(path: Path)
fun navigateToRoot(path: Path)
fun onAddStorage()
fun onEditStorage(storage: Storage)
fun onEditStandardDirectory(standardDirectory: StandardDirectory)
fun onEditBookmarkDirectory(bookmarkDirectory: BookmarkDirectory)
fun launchIntent(intent: Intent)
fun closeNavigationDrawer()
fun startActivity(intent: Intent)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@ import me.zhanghai.android.files.file.asFileSize
import me.zhanghai.android.files.ftpserver.FtpServerActivity
import me.zhanghai.android.files.settings.Settings
import me.zhanghai.android.files.settings.SettingsActivity
import me.zhanghai.android.files.settings.StandardDirectoryListActivity
import me.zhanghai.android.files.storage.AddStorageDialogActivity
import me.zhanghai.android.files.storage.FileSystemRoot
import me.zhanghai.android.files.storage.Storage
import me.zhanghai.android.files.storage.StorageVolumeListLiveData
import me.zhanghai.android.files.util.createIntent
import me.zhanghai.android.files.util.isMounted
import me.zhanghai.android.files.util.putArgs
import me.zhanghai.android.files.util.valueCompat

val navigationItems: List<NavigationItem?>
Expand Down Expand Up @@ -60,7 +63,10 @@ val navigationItems: List<NavigationItem?>

private val storageItems: List<NavigationItem>
@Size(min = 0)
get() = Settings.STORAGES.valueCompat.filter { it.isVisible }.map { StorageItem(it) }
get() =
Settings.STORAGES.valueCompat.filter { it.isVisible }.map {
if (it.path != null) PathStorageItem(it) else IntentStorageItem(it)
}

private abstract class PathItem(val path: Path) : NavigationItem() {
override fun isChecked(listener: Listener): Boolean = listener.currentPath == path
Expand All @@ -75,9 +81,9 @@ private abstract class PathItem(val path: Path) : NavigationItem() {
}
}

private class StorageItem(
private class PathStorageItem(
private val storage: Storage
) : PathItem(storage.path), NavigationRoot {
) : PathItem(storage.path!!), NavigationRoot {
init {
require(storage.isVisible)
}
Expand All @@ -95,13 +101,40 @@ private class StorageItem(
storage.linuxPath?.let { getStorageSubtitle(it, context) }

override fun onLongClick(listener: Listener): Boolean {
listener.onEditStorage(storage)
listener.launchIntent(storage.createEditIntent())
return true
}

override fun getName(context: Context): String = getTitle(context)
}

private class IntentStorageItem(
private val storage: Storage
) : NavigationItem() {
init {
require(storage.isVisible)
}

override val id: Long
get() = storage.id

override val iconRes: Int
@DrawableRes
get() = storage.iconRes

override fun getTitle(context: Context): String = storage.getName(context)

override fun onClick(listener: Listener) {
listener.launchIntent(storage.createIntent()!!)
listener.closeNavigationDrawer()
}

override fun onLongClick(listener: Listener): Boolean {
listener.launchIntent(storage.createEditIntent())
return true
}
}

private val storageVolumeItems: List<NavigationItem>
@Size(min = 0)
get() =
Expand Down Expand Up @@ -162,7 +195,7 @@ private class AddStorageItem : NavigationItem() {
context.getString(R.string.navigation_add_storage)

override fun onClick(listener: Listener) {
listener.onAddStorage()
listener.launchIntent(AddStorageDialogActivity::class.createIntent())
}
}

Expand Down Expand Up @@ -190,7 +223,7 @@ private class StandardDirectoryItem(
override fun getTitle(context: Context): String = standardDirectory.getTitle(context)

override fun onLongClick(listener: Listener): Boolean {
listener.onEditStandardDirectory(standardDirectory)
listener.launchIntent(StandardDirectoryListActivity::class.createIntent())
return true
}
}
Expand Down Expand Up @@ -311,23 +344,26 @@ private class BookmarkDirectoryItem(
override fun getTitle(context: Context): String = bookmarkDirectory.name

override fun onLongClick(listener: Listener): Boolean {
listener.onEditBookmarkDirectory(bookmarkDirectory)
listener.launchIntent(
EditBookmarkDirectoryDialogActivity::class.createIntent()
.putArgs(EditBookmarkDirectoryDialogFragment.Args(bookmarkDirectory))
)
return true
}
}

private val menuItems: List<NavigationItem>
@Size(3)
get() = listOf(
ActivityMenuItem(
IntentMenuItem(
R.drawable.shared_directory_icon_white_24dp, R.string.navigation_ftp_server,
FtpServerActivity::class.createIntent()
),
ActivityMenuItem(
IntentMenuItem(
R.drawable.settings_icon_white_24dp, R.string.navigation_settings,
SettingsActivity::class.createIntent()
),
ActivityMenuItem(
IntentMenuItem(
R.drawable.about_icon_white_24dp, R.string.navigation_about,
AboutActivity::class.createIntent()
)
Expand All @@ -340,7 +376,7 @@ private abstract class MenuItem(
override fun getTitle(context: Context): String = context.getString(titleRes)
}

private class ActivityMenuItem(
private class IntentMenuItem(
@DrawableRes iconRes: Int,
@StringRes titleRes: Int,
private val intent: Intent
Expand All @@ -349,8 +385,7 @@ private class ActivityMenuItem(
get() = intent.component.hashCode().toLong()

override fun onClick(listener: Listener) {
// TODO: startActivitySafe()?
listener.startActivity(intent)
listener.launchIntent(intent)
listener.closeNavigationDrawer()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (c) 2023 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/

package me.zhanghai.android.files.storage

import android.os.Bundle
import android.view.View
import androidx.fragment.app.commit
import me.zhanghai.android.files.app.AppActivity
import me.zhanghai.android.files.util.args
import me.zhanghai.android.files.util.putArgs

class AddDocumentManagerShortcutActivity : AppActivity() {
private val args by args<AddDocumentManagerShortcutFragment.Args>()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// Calls ensureSubDecor().
findViewById<View>(android.R.id.content)
if (savedInstanceState == null) {
val fragment = AddDocumentManagerShortcutFragment().putArgs(args)
supportFragmentManager.commit {
add(fragment, AddDocumentManagerShortcutFragment::class.java.name)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2023 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/

package me.zhanghai.android.files.storage

import android.os.Bundle
import androidx.annotation.StringRes
import androidx.fragment.app.Fragment
import kotlinx.parcelize.Parcelize
import me.zhanghai.android.files.R
import me.zhanghai.android.files.app.packageManager
import me.zhanghai.android.files.file.DocumentUri
import me.zhanghai.android.files.util.ParcelableArgs
import me.zhanghai.android.files.util.args
import me.zhanghai.android.files.util.createDocumentManagerViewDirectoryIntent
import me.zhanghai.android.files.util.finish
import me.zhanghai.android.files.util.showToast

class AddDocumentManagerShortcutFragment : Fragment() {
private val args by args<Args>()

override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)

val uri = args.uri
val hasDocumentManager = uri.value.createDocumentManagerViewDirectoryIntent()
.resolveActivity(packageManager) != null
if (hasDocumentManager) {
val documentManagerShortcut = DocumentManagerShortcut(
null, args.customNameRes?.let { getString(it) }, uri
)
Storages.addOrReplace(documentManagerShortcut)
} else {
showToast(R.string.activity_not_found)
}
finish()
}

@Parcelize
class Args(
@StringRes val customNameRes: Int?,
val uri: DocumentUri
) : ParcelableArgs
}
Loading

0 comments on commit 5bbd452

Please sign in to comment.