Reputation: 3567
I don't want my app to require any permissions, but I want the user to be able to select a file for reading. My app doesn't need arbitrary access to the filesystem. However, all openfiledialog implementations I have researched so far seem to assume permission to access external storage.
One workaround I can think of is to configure my app to be among the list of apps to open a certain type of file. I haven't tried this, but I hope this would work without permission to access external storage. However, user guidance would be less then ideal in this case. I would prefer a solution with a dialog and have the user pick the file.
I think this requirement does not undermine security, because the user has full control over the file my app can read. Is this possible somehow?
Upvotes: 5
Views: 3130
Reputation: 170
You don't need to ask read or write external storage when pick file, use below code for pick files and access data
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
intent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(Intent.createChooser(intent, "Select a File"),FILE_SELECT_CODE);
In onActivityResult method, get file data as InputStream and save in app storage. After upload or process a file, delete saved file.
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == FILE_SELECT_CODE && resultCode == RESULT_OK) {
try {
Uri uri = data.getData();
InputStream attachment = getContentResolver().openInputStream(uri);
if (attachment != null) {
String filename = getFileName(context.getContentResolver(), uri);
if (filename != null) {
File cacheFolder = new File(context.getExternalFilesDir(null), "cacheFolder");
if (!cacheFolder.exists()) cacheFolder.mkdir();
File file = new File(cacheFolder, filename);
FileOutputStream fileOutputStream = new FileOutputStream(file);
byte[] buffer = new byte[1024];
while (attachment.read(buffer) > 0) {
fileOutputStream.write(buffer);
}
fileOutputStream.close();
attachment.close();
uploadFile(file);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Upvotes: 0
Reputation: 525
Android 11 Resolve file access issue without use of MANAGE_EXTERNAL_STORAGE. I have added code for get doc file and Upload to server.
AndroidManifest
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:preserveLegacyExternalStorage="true"
android:requestLegacyExternalStorage="true"
</application>
Now, Add this lib in your project
https://github.com/FivesoftCode/FilePicker
Add below code to Activity/Fragment
FilePicker.from(activity)
.setFileTypes(FilePicker.IMAGE, FilePicker.VIDEO) //Set file types you want to pick.
.setAllowMultipleFiles(true) //Allow user to select multiple files
.setListener { files -> //Wait for results
if (files != null && files.size > 0) {
//Do something with uris.
for (items in files) {
val extension: String = getMimeType(activity!!,items)!!
if (extension == "pdf") {
val cacheDir: String = context!!.cacheDir.toString()
val getCopyFilePath = copyFileToInternalStorage(context!!,items,cacheDir)
Log.e("TAG", "getPathToUploadDoc: " + getCopyFilePath )
}
}
} else {
//Add msg here...
}
}
.setTitle("Pick a file from My Files")
.pick() //Open file picker
Add below method for get Mime Type
fun getMimeType(context: Context, uri: Uri): String? {
val extension: String?
//Check uri format to avoid null
extension = if (uri.scheme == ContentResolver.SCHEME_CONTENT) {
//If scheme is a content
val mime = MimeTypeMap.getSingleton()
mime.getExtensionFromMimeType(context.contentResolver.getType(uri))
} else {
//If scheme is a File
//This will replace white spaces with %20 and also other special characters. This will avoid returning null values on file name with spaces and special characters.
MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(File(uri.path)).toString())
}
return extension
}
fun getFiledetails(uri: Uri,context: Context,getCopyFilePath:String): NormalFile? {
// var result: String? = null
if (uri.scheme == "content") {
val cursor: Cursor = context.contentResolver.query(uri,
FileLoader.FILE_PROJECTION, null, null, null)!!
try {
if (cursor != null && cursor.moveToFirst()) {
// result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME))
val path: String = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA))
if (path != null && path != "") {
//Create a File instance
cursor.getLong(cursor.getColumnIndexOrThrow(BaseColumns._ID))
// cursor.getLong(cursor.getColumnIndexOrThrow(BaseColumns._ID)).toInt()
cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.TITLE))
// cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA))
cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE))
cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_ADDED))
cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MIME_TYPE))
}
}
} finally {
cursor.close()
}
}
/*if (result == null) {
result = uri.path
val cut = result!!.lastIndexOf('/')
if (cut != -1) {
result = result.substring(cut + 1)
}
}*/
return file
}
fun copyFileToInternalStorage(context: Context?,uri: Uri, newDirName: String): String? {
val returnCursor = context!!.contentResolver.query(
uri, arrayOf(
OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE
), null, null, null
)
/*
* Get the column indexes of the data in the Cursor,
* * move to the first row in the Cursor, get the data,
* * and display it.
* */
val nameIndex = returnCursor!!.getColumnIndex(OpenableColumns.DISPLAY_NAME)
val sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE)
returnCursor.moveToFirst()
val name = returnCursor.getString(nameIndex)
val size = java.lang.Long.toString(returnCursor.getLong(sizeIndex))
val output: File
output = if (newDirName != "") {
val dir = File(/*context!!.filesDir.toString() + "/" +*/ newDirName)
if (!dir.exists()) {
dir.mkdir()
}
File(/*context!!.filesDir.toString() + "/" +*/ newDirName + "/" + name)
} else {
File(context!!.filesDir.toString() + "/" + name)
}
try {
val inputStream: InputStream? = context!!.contentResolver.openInputStream(uri)
val outputStream = FileOutputStream(output)
var read = 0
val bufferSize = 1024
val buffers = ByteArray(bufferSize)
while (inputStream?.read(buffers).also { read = it!! } != -1) {
outputStream.write(buffers, 0, read)
}
inputStream?.close()
outputStream.close()
} catch (e: Exception) {
Log.e("Exception", e.message!!)
}
return output.path
}
For upload Doc
implementation 'net.gotev:uploadservice:2.1'
var uploadId = UUID.randomUUID().toString()
val url = ServerConfig.MAIN_URL
uploadReceiver.setDelegate(this)
uploadReceiver.setUploadID(uploadId)
val data = MultipartUploadRequest(mContext, uploadId, url)
.addFileToUpload(path, "attachment")
.addHeader("Authentication", getMD5EncryptedString())
.addParameter(USER_ID,1)
.setMaxRetries(5)
.startUpload()
fun getMD5EncryptedString(): String {
val encTarget = ServerConfig.AUTHENTICATE_VALUE //Any pwd
var mdEnc: MessageDigest? = null
try {
mdEnc = MessageDigest.getInstance("MD5")
} catch (e: NoSuchAlgorithmException) {
println("Exception while encrypting to md5")
e.printStackTrace()
}
mdEnc!!.update(encTarget.toByteArray(), 0, encTarget.length)
var md5 = BigInteger(1, mdEnc.digest()).toString(16)
while (md5.length < 32) {
md5 = "0$md5"
}
return md5
}
Upvotes: 0
Reputation: 1007584
However, all openfiledialog implementations I have researched so far seem to assume permission to access external storage.
Set your minSdkVersion
to 19, then use ACTION_OPEN_DOCUMENT
, part of the Storage Access Framework.
Or, if you need your minSdKVersion
to be below 19, use ACTION_GET_CONTENT
on the older devices.
You will get a Uri
back via onActivityResult()
. Use a ContentResolver
and methods like openInputStream()
to consume the content identified by that Uri
.
I haven't tried this, but I hope this would work without permission to access external storage
Only if you exclude file:
Uri
values. For example, an <intent-filter>
that supports only content:
Uri
values would work.
Upvotes: 9