Background
I maintain a library whose core functionality involves sharing programmatically-captured screenshots to external email applications.
I use a FileProvider
to accomplish this, which means my library's manifest contains a <provider>
tag:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.bugshaker.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
filepaths.xml
is defined as follows:
<paths>
<files-path path="bug-reports/" name="bug-reports" />
</paths>
A consumer of my library has an application which itself uses a FileProvider
to share files. My expectation was that it should be possible to allow both providers to share files if the consuming application used the following manifest <provider>
tag:
<provider
android:authorities="${applicationId}.fileprovider;${applicationId}.bugshaker.fileprovider"
android:exported="false"
android:grantUriPermissions="true"
android:name="android.support.v4.content.FileProvider"
tools:replace="android:authorities">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"
tools:replace="android:resource" />
</provider>
This manifest entry:
- specifies two
Provider
authorities,${applicationId}.fileprovider
(for application file sharing) and${applicationId}.bugshaker.fileprovider
(for library file sharing); - references an updated
filepaths.xml
, which contains separate directory definitions for application-generated files and library-generated files:
<paths>
<external-path
name="redacted"
path="" />
<files-path
name="bug-reports"
path="bug-reports/" />
</paths>
After building the application, we have confirmed that the generated manifest has had the correct nodes replaced with these updated values.
However, when the application using this configuration is assembled (successfully) and run, we see a crash on launch:
E: FATAL EXCEPTION: main
Process: com.stkent.bugshakertest, PID: 11636
java.lang.RuntimeException: Unable to get provider android.support.v4.content.FileProvider: java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.XmlResourceParser android.content.pm.PackageItemInfo.loadXmlMetaData(android.content.pm.PackageManager, java.lang.String)' on a null object reference
at android.app.ActivityThread.installProvider(ActivityThread.java:5856)
at android.app.ActivityThread.installContentProviders(ActivityThread.java:5445)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5384)
at android.app.ActivityThread.-wrap2(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1545)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6119)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.XmlResourceParser android.content.pm.PackageItemInfo.loadXmlMetaData(android.content.pm.PackageManager, java.lang.String)' on a null object reference
at android.support.v4.content.FileProvider.parsePathStrategy(FileProvider.java:583)
at android.support.v4.content.FileProvider.getPathStrategy(FileProvider.java:557)
at android.support.v4.content.FileProvider.attachInfo(FileProvider.java:375)
at android.app.ActivityThread.installProvider(ActivityThread.java:5853)
at android.app.ActivityThread.installContentProviders(ActivityThread.java:5445)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5384)
at android.app.ActivityThread.-wrap2(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1545)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6119)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
Using the debugger, I am able to see that the method FileProvider.parsePathStrategy
invokes PackageManager.resolveContentProvider
with the authority string "${applicationId}.fileprovider;${applicationId}.bugshaker.fileprovider"
. resolveContentProvider
then returns null, leading to this NPE.
If I manually call resolveContentProvider
while paused at this instruction and pass either "${applicationId}.fileprovider"
or "${applicationId}.bugshaker.fileprovider"
, resolveContentProvider
instead returns a non-null ProviderInfo
instance (which would seem to be the expected result).
This difference confuses me because the <provider>
element documentation states that multiple authorities are supported:
A list of one or more URI authorities that identify data offered by the content provider. Multiple authorities are listed by separating their names with a semicolon. To avoid conflicts, authority names should use a Java-style naming convention (such as com.example.provider.cartoonprovider). Typically, it's the name of the ContentProvider subclass that implements the provider
There is no default. At least one authority must be specified.
Questions
- Is it possible to have a single application expose a
FileProvider
with multiple authorities and file paths?- If so, what do I need to change to make that work?
- If not, are there other ways to configure file sharing within my library that avoid conflicts such as this one?
loadXmlMetaData()
, and I don't see that in theFileProvider
source code. An authority is provided toresolveContentProvider()
on the preceding line. Is that what you mean? If so,ProviderInfo
is providing the semicolon-delimited list, andFileProvider
doesn't seem to handle that. – ValorievalorizationFileProvider
, it would appear that they are not handling the multiple-authority scenario. They have hooks for having multiple path strategies by authority, but they never seem to parse the semicolon-delimited list. Probably untested. I have code in myStreamProvider
that does parse the list, but I haven't tested it either. :-( – ValorievalorizationparsePathStrategy
retrieves a newProviderInfo
instance at all when one is provided to theattachInfo
method that calls it. I see thatContentProvider
does split the authorities inattachInfo
and thatFileProvider
calls to super, but the multiple-authorities field insideContentProvider
does not seem to be accessible to subclasses at all. – LeilaleilahStreamProvider
in my sample app early this week and report back... – LeilaleilahContentProvider
; fewer still might be needed in concrete (i.e., registered in manifest form) by 2+ libraries or 1 other library and the app itself. Hence, I don't think there is a "normally". In this case,FileProvider
(andStreamProvider
) rely upon astatic
cache of data to manage multiple authorities, and so just using a simple subclass ofFileProvider
/StreamProvider
would be insufficient. – ValorievalorizationStreamProvider
I still get a crash on launch:Attempt to read from field 'android.os.Bundle android.content.pm.PackageItemInfo.metaData' on a null object reference
. Sounds similar. – Leilaleilah