还授予 Uri 读取权限,以访问在浏览器中使用 content:/// 方案打开的 html 链接的文件

Grant Uri read permission also to files linked by html opened in browser with content:/// scheme

要在网络浏览器中打开文件,并避免来自 Oreo 的 FileUriExposedException,我使用 FileProvider 和临时 URI 权限正确共享文件。 问题是要打开的 html 文件 包含一个 link 到另一个伴随文件 ,它与 file:///[...] 一起工作并且固定读取外部文件的权限目标应用程序中的内存,但不适用于 content:///[...] 因为临时权限当然只授予第一个文件而不是第二个文件。

有什么方法可以授予另一个文件临时读取权限吗?在用户 select 他们实际想要使用的应用程序之前,无需手动向每个合适的应用程序授予其他文件的权限?

目前的代码,使用各种浏览器的 itents:

protected Uri getUriFromFile(File file)
{
    return FileProvider.getUriForFile(
            this,
            "com.whatever.example.fileprovider",
            file);
}

protected void openInWebBroser(File file, File linkedFile)
{   
    Uri uri = getUriFromFile(file);
    Uri linkedUri = getUriFromFile(linkedFile);

    // Firefox and html viewer
    Intent webIntent1 = new Intent(Intent.ACTION_VIEW);
    webIntent1.setDataAndType(uri, "text/html");
    webIntent1.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    // TODO somehow add also linkedUri, not to be open right now but to be added to permitted???

    // Chrome
    Intent webIntent2 = new Intent(Intent.ACTION_VIEW);
    webIntent2.setDataAndType(uri, "multipart/related");
    webIntent2.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

    // Default android browser
    Intent webIntent3 = new Intent(Intent.ACTION_VIEW);         
    webIntent3.setDataAndType(uri, "text/html");
    webIntent3.setClassName("com.android.browser", "com.android.browser.BrowserActivity");
    webIntent3.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

    if(getPackageManager().queryIntentActivities(webIntent1, 0).size() > 0)
    {
        Intent chooserIntent = Intent.createChooser(webIntent1, null);
        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { webIntent2, webIntent3 });          
        startActivity(chooserIntent);
    }
    else if(getPackageManager().queryIntentActivities(webIntent2, 0).size() > 0)
    {
        Intent chooserIntent = Intent.createChooser(webIntent2, null);
        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { webIntent3 });          
        startActivity(chooserIntent);
    }
    else if(getPackageManager().queryIntentActivities(webIntent3, 0).size() > 0)
    {
        Intent chooserIntent = Intent.createChooser(webIntent3, null);          
        startActivity(chooserIntent);
    }
    else
    {
        // ... error management
    }
}

好吧,没有答案,也没有更好的选择,我能想到的最好办法是预先授权任何可用的浏览器读取链接文件。

至于 revokeUriPermission(),虽然文档坚持您必须调用它,但从原始应用程序调用它似乎不正确(例如,在 onDestroy() 中,当 isFinishing() ?):如果原始应用程序在浏览器仍处于打开状态时关闭,会发生什么情况? (当您使用意图时,当目标应用程序展开堆栈时,权限将被撤销。)文档指出权限在设备重新启动之前有效。这似乎比其他选择更好。意见?

有没有办法只针对用户实际选择的应用执行此操作? (无需重新实现整个选择器。)

protected Uri getUriFromFile(File file)
{
    return FileProvider.getUriForFile(
            this,
            "com.whatever.example.fileprovider",
            file);
}

protected void openInWebBroser(File file, File linkedFile)
{   
    Uri uri = getUriFromFile(file);
    Uri linkedUri = null;
    if( linkedFile!= null )
        linkedUri = getUriFromFile(linkedFile);

    // Firefox and html viewer
    Intent webIntent1 = new Intent(Intent.ACTION_VIEW);
    webIntent1.setDataAndType(uri, "text/html");
    webIntent1.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

    // Chrome
    Intent webIntent2 = new Intent(Intent.ACTION_VIEW);
    webIntent2.setDataAndType(uri, "multipart/related");
    webIntent2.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

    // Default android browser
    Intent webIntent3 = new Intent(Intent.ACTION_VIEW);         
    webIntent3.setDataAndType(uri, "text/html");
    webIntent3.setClassName("com.android.browser", "com.android.browser.BrowserActivity");
    webIntent3.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

    // Pre-grant access to linkedUri for any candidate
    if( linkedUri != null )
    {
        Intent[] intents = new Intent[]{ webIntent1, webIntent2, webIntent3 };

        for( Intent intent : intents )
            for( ResolveInfo info : getPackageManager ().queryIntentActivities(intent, 0) )
                grantUriPermission(info.activityInfo.packageName, linkedUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
    }


    if(getPackageManager().queryIntentActivities(webIntent1, 0).size() > 0)
    {
        Intent chooserIntent = Intent.createChooser(webIntent1, null);
        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { webIntent2, webIntent3 });          
        startActivity(chooserIntent);
    }
    else if(getPackageManager().queryIntentActivities(webIntent2, 0).size() > 0)
    {
        Intent chooserIntent = Intent.createChooser(webIntent2, null);
        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { webIntent3 });          
        startActivity(chooserIntent);
    }
    else if(getPackageManager().queryIntentActivities(webIntent3, 0).size() > 0)
    {
        Intent chooserIntent = Intent.createChooser(webIntent3, null);          
        startActivity(chooserIntent);
    }
    else
    {
        // ... error management
    }
}