user1785730
user1785730

Reputation: 3567

Android: Read a file without external storage permission

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

Answers (3)

Vignesh
Vignesh

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

viral 9966
viral 9966

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

CommonsWare
CommonsWare

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

Related Questions