Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

external storage support #8

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .idea/compiler.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions .idea/deploymentTargetDropDown.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions .idea/jarRepositories.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/kotlinc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 11 additions & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 23 additions & 11 deletions UnicornFilePicker/build.gradle
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
plugins {
id 'com.android.library'
}
apply plugin: 'kotlin-android'

android {
compileSdkVersion 30
buildToolsVersion "30.0.2"
compileSdk 33
viewBinding{
enabled true
}
Expand All @@ -13,12 +13,13 @@ android {

defaultConfig {
minSdkVersion 21
targetSdkVersion 30
targetSdk 33
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
vectorDrawables.useSupportLibrary = true
}

buildTypes {
Expand All @@ -28,9 +29,13 @@ android {
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}

sourceSets {
main {
res {
Expand All @@ -42,10 +47,17 @@ android {

dependencies {

implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.documentfile:documentfile:1.0.1'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
implementation "androidx.core:core-ktx:1.10.1"
//noinspection GradleDependency
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
repositories {
mavenCentral()
}
7 changes: 5 additions & 2 deletions UnicornFilePicker/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="abhishekti7.unicorn.filepicker">

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<application>
<activity android:name="abhishekti7.unicorn.filepicker.ui.FilePickerActivity" />
<activity android:name="abhishekti7.unicorn.filepicker.ui.FilePickerActivity" android:exported="true"/>
</application>

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public final class ConfigBuilder {
private boolean showOnlyDir = false;

@StyleRes
private int themeId = R.style.UnicornFilePicker_Default;
private int themeId = R.style.UnicornFilePicker_Default_AppTheme;

private final UnicornFilePicker unicornFilePicker;
private ArrayList<String> extensionFilters;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package abhishekti7.unicorn.filepicker.adapters

import abhishekti7.unicorn.filepicker.R
import abhishekti7.unicorn.filepicker.storage.StorageDirectoryParcelable
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.ImageView

import android.widget.TextView
import java.lang.IllegalArgumentException


class StorageAdapter(context: Context,private val items : List<StorageDirectoryParcelable>) : ArrayAdapter<StorageDirectoryParcelable>(context, R.layout.unicorn_storage_picker_item, items ) {
private var inflater : LayoutInflater? = null


override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View? {
return rowView(convertView, position)
}

override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
return rowView(convertView, position) ?: throw IllegalArgumentException("Row view is null")
}

private fun rowView(convertView: View?, position: Int): View? {
val rowItem: StorageDirectoryParcelable? = getItem(position)
val holder: ViewHolder
var rowView : View? = convertView
if (rowView == null) {
holder = ViewHolder()
inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
rowView = inflater?.inflate(R.layout.unicorn_storage_picker_item, null)
rowView?.let{
holder.txtTitle = rowView.findViewById<View>(R.id.title) as TextView
holder.icon = rowView.findViewById<View>(R.id.icon) as ImageView
rowView.tag = holder
}
} else {
holder = rowView.tag as ViewHolder
}
holder.icon?.setImageResource(rowItem?.iconRes ?: -1)
holder.txtTitle?.text = rowItem?.name ?: ""
return rowView
}

private class ViewHolder {
var txtTitle: TextView? = null
var icon: ImageView? = null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package abhishekti7.unicorn.filepicker.filesystem

import android.annotation.TargetApi
import android.content.Context
import android.net.Uri
import android.os.Build
import android.preference.PreferenceManager
import android.util.Log
import androidx.documentfile.provider.DocumentFile
import java.io.File
import java.io.IOException
import java.util.ArrayList

object ExternalSdCardOperation {
val LOG = "ExternalSdCardOperation"

/**
* Get a DocumentFile corresponding to the given file (for writing on ExtSdCard on Android 5). If
* the file is not existing, it is created.
*
* @param file The file.
* @param isDirectory flag indicating if the file should be a directory.
* @return The DocumentFile
*/
@JvmStatic
fun getDocumentFile(
file: File,
isDirectory: Boolean,
context: Context
): DocumentFile? {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) return DocumentFile.fromFile(file)
val baseFolder = getExtSdCardFolder(file, context)
var originalDirectory = false
if (baseFolder == null) {
return null
}
var relativePath: String? = null
try {
val fullPath = file.canonicalPath
if (baseFolder != fullPath) {
relativePath = fullPath.substring(baseFolder.length + 1)
} else {
originalDirectory = true
}
} catch (e: IOException) {
return null
}

val preferenceUri = PreferenceManager.getDefaultSharedPreferences(context)
.getString(PreferencesConstants.PREFERENCE_URI, null)
var treeUri: Uri? = null
if (preferenceUri != null) {
treeUri = Uri.parse(preferenceUri)
}
if (treeUri == null) {
return null
}

// start with root of SD card and then parse through document tree.
var document = DocumentFile.fromTreeUri(context, treeUri)
if (originalDirectory || relativePath == null) {
return document
}

val parts = relativePath.split("/").toTypedArray()
for (i in parts.indices) {
if (document == null) {
return null
}

var nextDocument = document.findFile(parts[i])
if (nextDocument == null) {
nextDocument = if (i < parts.size - 1 || isDirectory) {
document.createDirectory(parts[i])
} else {
document.createFile("image", parts[i])
}
}
document = nextDocument
}

return document
}

/**
* Get a list of external SD card paths. (Kitkat or higher.)
*
* @return A list of external SD card paths.
*/
@JvmStatic
@TargetApi(Build.VERSION_CODES.KITKAT)
private fun getExtSdCardPaths(context: Context): Array<String> {
val paths: MutableList<String> = ArrayList()
for (file in context.getExternalFilesDirs("external")) {
if (file != null && file != context.getExternalFilesDir("external")) {
val index = file.absolutePath.lastIndexOf("/Android/data")
if (index < 0) {
Log.w(LOG, "Unexpected external file dir: " + file.absolutePath)
} else {
var path = file.absolutePath.substring(0, index)
try {
path = File(path).canonicalPath
} catch (e: IOException) {
// Keep non-canonical path.
}
paths.add(path)
}
}
}
if (paths.isEmpty()) paths.add("/storage/sdcard1")
return paths.toTypedArray()
}

@JvmStatic
@TargetApi(Build.VERSION_CODES.KITKAT)
fun getExtSdCardPathsForActivity(context: Context): Array<String> {
val paths: MutableList<String> = ArrayList()
for (file in context.getExternalFilesDirs("external")) {
if (file != null) {
val index = file.absolutePath.lastIndexOf("/Android/data")
if (index < 0) {
Log.w(LOG, "Unexpected external file dir: " + file.absolutePath)
} else {
var path = file.absolutePath.substring(0, index)
try {
path = File(path).canonicalPath
} catch (e: IOException) {
// Keep non-canonical path.
}
paths.add(path)
}
}
}
if (paths.isEmpty()) paths.add("/storage/sdcard1")
return paths.toTypedArray()
}

/**
* Determine the main folder of the external SD card containing the given file.
*
* @param file the file.
* @return The main folder of the external SD card containing this file, if the file is on an SD
* card. Otherwise, null is returned.
*/
@JvmStatic
@TargetApi(Build.VERSION_CODES.KITKAT)
public fun getExtSdCardFolder(file: File, context: Context): String? {
val extSdPaths = getExtSdCardPaths(context)
try {
for (i in extSdPaths.indices) {
if (file.canonicalPath.startsWith(extSdPaths[i])) {
return extSdPaths[i]
}
}
} catch (e: IOException) {
return null
}
return null
}

/**
* Determine if a file is on external sd card. (Kitkat or higher.)
*
* @param file The file.
* @return true if on external sd card.
*/
@JvmStatic
@TargetApi(Build.VERSION_CODES.KITKAT)
fun isOnExtSdCard(file: File, c: Context): Boolean {
return getExtSdCardFolder(file, c) != null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package abhishekti7.unicorn.filepicker.filesystem

import java.io.File
/** Functions that deal with files */
object FileUtils {

fun canListFiles(f: File): Boolean {
return f.canRead() && f.isDirectory
}


}
Loading