4

I start an ACTION_GET_CONTENT intent in order to pick a PDF:

override fun routeToFilePicker() {
    val intent = Intent()
    intent.type = MediaType.PDF.toString()
    intent.action = Intent.ACTION_GET_CONTENT
    activity.startActivityForResult(
        Intent.createChooser(intent, "Select PDF"),
        REQUEST_CODE_PDF_PICKER
    )
}

Then on onActivityResult I try to create a PDF from the Uri (content//:path):

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE_PDF_PICKER ) {
        data?.data?.let { pdfUri: Uri ->
            val pdfFile: File = pdfUri.toFile() <-- chrash
            ...
        }
    }
}

pdfUri.toFile() causes a fatal Exception:

java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=1003, result=-1, data=Intent { dat=content://com.android.providers.downloads.documents/document/3569 flg=0x1 }} to activity {my.package.name.activity}: java.lang.IllegalArgumentException: Uri lacks 'file' scheme: content://com.android.providers.downloads.documents/document/3569

Caused by: java.lang.IllegalArgumentException: Uri lacks 'file' scheme: content://com.android.providers.downloads.documents/document/3569

I need a File in order to convert the pages into Images.
How can I get the PDF as a File from the Uri returned by MediaStore?

iknow
  • 8,358
  • 12
  • 41
  • 68
Joel Broström
  • 3,530
  • 1
  • 34
  • 61
  • Do you really need a File instance or just access to the data via a InputStream or the like? – CodeRed Apr 11 '20 at 17:21
  • 1
    i see few usecases where u actually need a File, or file path. I'm afraid I counldn't find the solution. very strange – M. Usman Khan Jul 22 '20 at 16:07
  • Yes. I can't remember the exact use case but It had something to do with uploading PDFs to server after editing them. We also had to extract images from the PDF and for that we needed an actual file. – Joel Broström Jul 23 '20 at 11:12
  • 1
    @M.UsmanKhan "i see few usecases where u actually need a File, or file path" -- there is no requirement that `ACTION_GET_CONTENT` return a `Uri` that represents a file on the filesystem. See https://stackoverflow.com/a/59123287/115145 and https://stackoverflow.com/a/59911702/115145 and https://stackoverflow.com/a/56308643/115145 for more. – CommonsWare Jul 23 '20 at 14:50
  • @CommonsWare App has the File excess permissions from user already. Also, how do we get the user to choose a video file (large) and get its path without copying it? we only get content URI right? i couldn't find the solution to this simple problem – M. Usman Khan Jul 24 '20 at 07:40
  • @M.UsmanKhan: "App has the File excess permissions from user already" -- that does not matter. "Also, how do we get the user to choose a video file (large) and get its path without copying it?" -- the closest thing that you can do is present a list of videos yourself, based on a query for videos from `MediaStore`. Then, try to use the value of the `DATA` column from `MediaStore` as a filesystem path. This will be unreliable, as the `MediaStore` has access to files that your app does not (e.g., on removable storage), and it is unclear if the `DATA` column will be usable in the future. – CommonsWare Jul 24 '20 at 11:11
  • @CommonsWare thanks. But what if my users are okay to give me any kind of permission? Why is it so hard to achieve this simple goal. I guess i'll be forced to use third party library – M. Usman Khan Jul 24 '20 at 14:06
  • 3
    @M.UsmanKhan: "But what if my users are okay to give me any kind of permission?" -- that does not matter. "I guess i'll be forced to use third party library" -- that is not going to help. "Why is it so hard to achieve this simple goal" -- because files on a filesystem are merely one data source among many, with content on the Internet being a prominent alternative. So, Google decided to go with abstractions, so the data can come from any source, local or remote. – CommonsWare Jul 24 '20 at 14:18

3 Answers3

5

This is how I'm getting pdf as file :

val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
                            type = "application/pdf"
                            addCategory(Intent.CATEGORY_OPENABLE)
                            flags = flags or Intent.FLAG_GRANT_READ_URI_PERMISSION
                        }
                        startActivityForResult(intent, 111)

Inside your OnActivityResult(requestCode:Int,resultCode:Int,data:Intent?)

if (resultCode == Activity.RESULT_OK) {
            when (requestCode) {
//                101 -> {
//                    data?.data?.also { uri ->
//                        Log.i(TAG, "Uri: $uri")
//                        baseAdapter?.add(ImageArray(null, null, uri.toString()))
//                    }
//                }
                111 -> {
                    data?.data?.also { documentUri ->
                        baseActivity.contentResolver?.takePersistableUriPermission(
                                documentUri,
                                Intent.FLAG_GRANT_READ_URI_PERMISSION
                        )
                        val file = DocumentUtils.getFile(baseActivity,documentUri)//use pdf as file 
                    }
                }
            }

        }

Singleton class to covert Uri to file:

object DocumentUtils {
    fun getFile(mContext: BaseActivity?, documentUri: Uri): File {
        val inputStream = mContext?.contentResolver?.openInputStream(documentUri)
        var file =  File("")
        inputStream.use { input ->
            file =
                File(mContext?.cacheDir, System.currentTimeMillis().toString()+".pdf")
            FileOutputStream(file).use { output ->
                val buffer =
                    ByteArray(4 * 1024) // or other buffer size
                var read: Int = -1
                while (input?.read(buffer).also {
                        if (it != null) {
                            read = it
                        }
                    } != -1) {
                    output.write(buffer, 0, read)
                }
                output.flush()
            }
        }
        return file
    }
} 

P.S: Do not forget to ask permission at runtime

Manifest.permission.WRITE_EXTERNAL_STORAGE
Manifest.permission.READ_EXTERNAL_STORAGE
chand mohd
  • 2,363
  • 1
  • 14
  • 27
2

With the latest API updates, Android has pushed usage of Content resolver for file related processing.

Using the path received , the same needs to be resolved using Content resolver..

Samples are available in Google Github

To give an overview, the URI received after file selection needs to be passed like below in Fragment.

Create a Fragment for showing PDF. And there will be a ViewModel associated.

val parcelFileDescriptor = activity?.contentResolver?.openFileDescriptor(fileURI, "r")

In ViewModel, with the help of Coroutine's, we will use process like below

val scope = CoroutineScope(executor.asCoroutineDispatcher() + job)

scope.launch {
            openPdfRenderer()
            showPage(0)
        }

fun openPdfRenderer() {
        
        pdfRenderer = PdfRenderer(fileURI!!) //android.graphics.pdf.PdfRenderer
    }

fun showPage(index: Int) {

        currentPage?.let { page ->
            currentPage = null
            page.close()
        }
        pdfRenderer?.let { renderer ->

            val page = renderer.openPage(index).also {
                currentPage = it
            }
            val bitmap = Bitmap.createBitmap(page.width, page.height, Bitmap.Config.ARGB_8888)
            page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_PRINT)
            _pageBitmap.postValue(bitmap)
            val count = renderer.pageCount
            _pageInfo.postValue(index to count)
            _previousEnabled.postValue(index > 0)
            _nextEnabled.postValue(index + 1 < count)
        }
    }

Full sample source can be found over Google Github Sample Link

I agree, this may seem to be an overhead ! But so are the changes in Android Geography due to multiple reasons.

Happy Coding ! Cheers !

Sreehari
  • 5,621
  • 2
  • 25
  • 59
  • This still does not give me a file, but just the URI which is then used to render a pdf? What we want is a way to get the actual file, or create a copy of it, that we can operate on. – Joel Broström Jul 29 '20 at 07:50
  • For this question "I need a File in order to convert the pages into Images." This is already done in the sample given. And shown in PDF Viewer fragment as image itself. But it does not exposes creating or copying a file for you to manipulate. – Sreehari Jul 29 '20 at 11:49
  • If you just want to show preview of PDF content in a Fragment, this would suffice. – Sreehari Jul 29 '20 at 12:22
-1
    public static File getFileFromUri(Uri uri, Context context) {
        if (uri == null) {
            return null;
        }
        switch (uri.getScheme()) {
            case "content":
                return getFileFromContentUri(uri, context);
            case "file":
                return new File(uri.getPath());
            default:
                return null;
        }
    }
    
    private static File getFileFromContentUri(Uri contentUri, Context context) {
        if (contentUri == null) {
            return null;
        }
        File file = null;
        String filePath;
        String fileName;
        String[] filePathColumn = {MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DISPLAY_NAME};
        ContentResolver contentResolver = context.getContentResolver();
        Cursor cursor = contentResolver.query(contentUri, filePathColumn, null,
                null, null);
        if (cursor != null) {
            cursor.moveToFirst();
            filePath = cursor.getString(cursor.getColumnIndex(filePathColumn[0]));
            fileName = cursor.getString(cursor.getColumnIndex(filePathColumn[1]));
            cursor.close();
            if (!TextUtils.isEmpty(filePath)) {
                file = new File(filePath);
            }
            if (!file.exists() || file.length() <= 0 || TextUtils.isEmpty(filePath)) {
                filePath = getPathFromInputStreamUri(context, contentUri, fileName);
            }
            if (!TextUtils.isEmpty(filePath)) {
                file = new File(filePath);
            }
        }
        return file;
    }

simon5678
  • 249
  • 1
  • 5