Encrypted overlay filesystems implementation for Android.
Also available on GitHub: https://github.com/hardcore-sushi/DroidFS
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
233 lines
9.4 KiB
233 lines
9.4 KiB
package sushi.hardcore.droidfs.adapters |
|
|
|
import android.annotation.SuppressLint |
|
import android.graphics.Bitmap |
|
import android.graphics.drawable.Drawable |
|
import android.util.LruCache |
|
import android.view.View |
|
import android.view.ViewGroup |
|
import android.widget.ImageView |
|
import android.widget.LinearLayout |
|
import android.widget.TextView |
|
import androidx.appcompat.app.AppCompatActivity |
|
import androidx.core.graphics.drawable.toBitmap |
|
import androidx.recyclerview.widget.RecyclerView |
|
import com.bumptech.glide.Glide |
|
import com.bumptech.glide.request.target.DrawableImageViewTarget |
|
import com.bumptech.glide.request.transition.Transition |
|
import sushi.hardcore.droidfs.ConstValues |
|
import sushi.hardcore.droidfs.GocryptfsVolume |
|
import sushi.hardcore.droidfs.R |
|
import sushi.hardcore.droidfs.explorers.ExplorerElement |
|
import sushi.hardcore.droidfs.util.PathUtils |
|
import java.text.DateFormat |
|
import java.util.* |
|
|
|
class ExplorerElementAdapter( |
|
val activity: AppCompatActivity, |
|
val gocryptfsVolume: GocryptfsVolume?, |
|
val onExplorerElementClick: (Int) -> Unit, |
|
val onExplorerElementLongClick: (Int) -> Unit, |
|
val thumbnailMaxSize: Long, |
|
) : SelectableAdapter<ExplorerElement>() { |
|
val dateFormat: DateFormat = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.getDefault()) |
|
var explorerElements = listOf<ExplorerElement>() |
|
@SuppressLint("NotifyDataSetChanged") |
|
set(value) { |
|
field = value |
|
thumbnailsCache?.evictAll() |
|
notifyDataSetChanged() |
|
} |
|
var isUsingListLayout = true |
|
private var thumbnailsCache: LruCache<String, Bitmap>? = null |
|
|
|
init { |
|
if (gocryptfsVolume != null) { |
|
thumbnailsCache = LruCache((Runtime.getRuntime().maxMemory() / 1024 / 8).toInt()) |
|
} |
|
} |
|
|
|
override fun getItems(): List<ExplorerElement> { |
|
return explorerElements |
|
} |
|
|
|
override fun toggleSelection(position: Int): Boolean { |
|
return if (!explorerElements[position].isParentFolder) { |
|
super.toggleSelection(position) |
|
} else { |
|
false |
|
} |
|
} |
|
|
|
override fun onItemClick(position: Int): Boolean { |
|
onExplorerElementClick(position) |
|
return super.onItemClick(position) |
|
} |
|
|
|
override fun onItemLongClick(position: Int): Boolean { |
|
onExplorerElementLongClick(position) |
|
return super.onItemLongClick(position) |
|
} |
|
|
|
override fun isSelectable(position: Int): Boolean { |
|
return !explorerElements[position].isParentFolder |
|
} |
|
|
|
open class ExplorerElementViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { |
|
private val textElementName by lazy { |
|
itemView.findViewById<TextView>(R.id.text_element_name) |
|
} |
|
protected val textElementSize: TextView by lazy { |
|
itemView.findViewById(R.id.text_element_size) |
|
} |
|
protected val textElementMtime: TextView by lazy { |
|
itemView.findViewById(R.id.text_element_mtime) |
|
} |
|
protected val icon: ImageView by lazy { |
|
itemView.findViewById(R.id.icon_element) |
|
} |
|
private val selectableContainer: LinearLayout by lazy { |
|
itemView.findViewById(R.id.selectable_container) |
|
} |
|
|
|
open fun bind(explorerElement: ExplorerElement, position: Int) { |
|
textElementName.text = explorerElement.name |
|
(bindingAdapter as ExplorerElementAdapter?)?.setSelectable(selectableContainer, itemView, position) |
|
} |
|
} |
|
|
|
open class RegularElementViewHolder(itemView: View) : ExplorerElementViewHolder(itemView) { |
|
open fun bind(explorerElement: ExplorerElement, position: Int, isSelected: Boolean) { |
|
super.bind(explorerElement, position) |
|
textElementSize.text = PathUtils.formatSize(explorerElement.size) |
|
(bindingAdapter as ExplorerElementAdapter?)?.let { |
|
textElementMtime.text = it.dateFormat.format(explorerElement.mTime) |
|
} |
|
} |
|
} |
|
|
|
class FileViewHolder(itemView: View) : RegularElementViewHolder(itemView) { |
|
var displayThumbnail = true |
|
var target: DrawableImageViewTarget? = null |
|
|
|
private fun loadThumbnail(fullPath: String, adapter: ExplorerElementAdapter) { |
|
adapter.gocryptfsVolume?.let { volume -> |
|
displayThumbnail = true |
|
Thread { |
|
volume.loadWholeFile(fullPath, maxSize = adapter.thumbnailMaxSize).first?.let { |
|
if (displayThumbnail) { |
|
adapter.activity.runOnUiThread { |
|
if (displayThumbnail && !adapter.activity.isFinishing) { |
|
target = Glide.with(adapter.activity).load(it).skipMemoryCache(true).into(object : DrawableImageViewTarget(icon) { |
|
override fun onResourceReady( |
|
resource: Drawable, |
|
transition: Transition<in Drawable>? |
|
) { |
|
val bitmap = resource.toBitmap() |
|
adapter.thumbnailsCache!!.put(fullPath, bitmap.copy(bitmap.config, true)) |
|
super.onResourceReady(resource, transition) |
|
target = null |
|
} |
|
}) |
|
} |
|
} |
|
} |
|
} |
|
}.start() |
|
} |
|
} |
|
|
|
private fun setThumbnailOrDefaultIcon(fullPath: String, defaultIconId: Int) { |
|
var setDefaultIcon = true |
|
(bindingAdapter as ExplorerElementAdapter?)?.let { adapter -> |
|
adapter.thumbnailsCache?.let { |
|
val thumbnail = it.get(fullPath) |
|
if (thumbnail != null) { |
|
icon.setImageBitmap(thumbnail) |
|
setDefaultIcon = false |
|
} else { |
|
loadThumbnail(fullPath, adapter) |
|
} |
|
} |
|
} |
|
if (setDefaultIcon) { |
|
icon.setImageResource(defaultIconId) |
|
} |
|
} |
|
|
|
override fun bind(explorerElement: ExplorerElement, position: Int, isSelected: Boolean) { |
|
super.bind(explorerElement, position, isSelected) |
|
when { |
|
ConstValues.isImage(explorerElement.name) -> { |
|
setThumbnailOrDefaultIcon(explorerElement.fullPath, R.drawable.icon_file_image) |
|
} |
|
ConstValues.isVideo(explorerElement.name) -> { |
|
setThumbnailOrDefaultIcon(explorerElement.fullPath, R.drawable.icon_file_video) |
|
} |
|
else -> icon.setImageResource( |
|
when { |
|
ConstValues.isText(explorerElement.name) -> R.drawable.icon_file_text |
|
ConstValues.isPDF(explorerElement.name) -> R.drawable.icon_file_pdf |
|
ConstValues.isAudio(explorerElement.name) -> R.drawable.icon_file_audio |
|
else -> R.drawable.icon_file_unknown |
|
} |
|
) |
|
} |
|
} |
|
} |
|
|
|
class DirectoryViewHolder(itemView: View) : RegularElementViewHolder(itemView) { |
|
override fun bind(explorerElement: ExplorerElement, position: Int, isSelected: Boolean) { |
|
super.bind(explorerElement, position, isSelected) |
|
icon.setImageResource(R.drawable.icon_folder) |
|
} |
|
} |
|
|
|
class ParentFolderViewHolder(itemView: View): ExplorerElementViewHolder(itemView) { |
|
override fun bind(explorerElement: ExplorerElement, position: Int) { |
|
super.bind(explorerElement, position) |
|
textElementSize.text = "" |
|
textElementMtime.setText(R.string.parent_folder) |
|
icon.setImageResource(R.drawable.icon_folder) |
|
} |
|
} |
|
|
|
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) { |
|
if (holder is FileViewHolder) { |
|
//cancel pending thumbnail display |
|
holder.displayThumbnail = false |
|
holder.target?.let { |
|
Glide.with(activity).clear(it) |
|
} |
|
} |
|
} |
|
|
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { |
|
val view = activity.layoutInflater.inflate( |
|
if (isUsingListLayout) { |
|
R.layout.adapter_explorer_element_list |
|
} else { |
|
R.layout.adapter_explorer_element_grid |
|
}, parent, false |
|
) |
|
return when (viewType) { |
|
ExplorerElement.REGULAR_FILE_TYPE -> FileViewHolder(view) |
|
ExplorerElement.DIRECTORY_TYPE -> DirectoryViewHolder(view) |
|
ExplorerElement.PARENT_FOLDER_TYPE -> ParentFolderViewHolder(view) |
|
else -> throw IllegalArgumentException() |
|
} |
|
} |
|
|
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { |
|
val element = explorerElements[position] |
|
if (element.isParentFolder) { |
|
(holder as ParentFolderViewHolder).bind(element, position) |
|
} else { |
|
(holder as RegularElementViewHolder).bind(element, position, selectedItems.contains(position)) |
|
} |
|
} |
|
|
|
override fun getItemViewType(position: Int): Int { |
|
return explorerElements[position].elementType.toInt() |
|
} |
|
} |