Native Android image sharing in Unity using FileProvider


Native Android in Unity
While implementing native sharing using my previous post, Native Android image sharing in Unity we saw an exception “android.os.FileUriExposedException” in Android 8 (Oreo) and above.

Since Google changed the sharing way using FileProvider to make it more secure, we can not share the file object directly to any other Application. We need to use the FileProvider and grant read URI permission to make it accessible for any other app.
In this blog, will guide step by step to enable image sharing to any other app using FileProvider. After this, you can share your screenshots or image to any other apps on above Android 8 (Oreo) also. Please stay with me as it's going to be a long journey.

But before that, I would suggest to please go through my last blogs that will give an insight into what we are trying to achieve.

If you have not read the previous blogs, I would strongly recommend to read them first. You can read them on the links below.

To use file provider in Unity and share a screenshot/image, we need to follow below steps.

  1. Create a project and separate module in Native Android using Android Studio.
  2. Define paths in xml file in native android.
  3. Define Provider in Android manifest file.
  4. Export it as an Android archive file (.aar) from Android Studio.
  5. Enable Gradle dependency in Unity project.
  6. Add Support dependency in the gradle file.
  7. Change sharing intent code in the C# file.

We can skip the first 4 steps and can use “.aar” file generated by me (which I will share) also but to give everyone an insight about what is happening exactly,

Let’s go step by step.

1. Create a project and separate module in Native Android using Android Studio

Since putting a raw Android XML is not allowed in Unity, we need a separate Android module containing our code and export it as an aar file to use it in Unity. To create the same we need Android studio. I am not going to follow on how to download and setup android studio. You can Google it and set it up.

Create a new project in Android studio.

Native Android image sharing in Unity using FileProvider
Select the minimum API level same as your unity project and fill all remaining fields. These fields really don’t matter as we are going to create a separate library in this project. Click on finish.

Create new “Android Library Module” in the same project by clicking File > New > New Module.

Native Android image sharing in Unity using FileProvider
Name the library and click finish.

Native Android image sharing in Unity using FileProvider
Once this is done, we can remove the unnecessary dependencies and folders like “test” and “androidTest” folder. We can also remove the string.xml file. We can even remove the testing dependency from build.gradle of our library.
  
//testInstrumentationRunner
// "android.support.test.runner.AndroidJUnitRunner"
and
//testImplementation 'junit:junit:4.12'
//androidTestImplementation 'com.android.support.test:runner:1.0.2'
//androidTestImplementation
// 'com.android.support.test.espresso:espresso-core:3.0.2'
Please note that we need appcompat support library. The final build.gradle will be
  
apply plugin: 'com.android.library'

android {
compileSdkVersion 28

defaultConfig {
minSdkVersion 14
targetSdkVersion 28
versionCode 1
versionName "1.0"

testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
}

2. Define paths in xml file in native android

Create a new XML resource file with name provider_paths.xml in our library.

Native Android image sharing in Unity using FileProvider
replace the below code in the created provider_paths.xml file and save it.
  
<?xml version="1.0" encoding="utf-8"? >
<paths xmlns:android="http://schemas.android.com/apk/res/android" >
<external-path name="external_files" path="."/ >
</paths >

3. Define Provider in Android manifest file.

Define Provider in the manifest.xml file of the library.
  
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.agrawalsuneet.unitysharing" >
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" / >
<application >
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true" >
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" / >
</provider >
</application >
</manifest >
Please note that we are adding write external storage permission here only but you can add it in unity also. I added here so that we don’t miss it.

4. Export it as an Android archive file (.aar) from Android Studio

To export the aar file out of our library, run the below command in the terminal on our root folder of android project.
  
./gradlew assembleRelease
This will generate a release android archive (.aar) file in unitysharing/build/outputs/aar/ folder.

Native Android image sharing in Unity using FileProvider

5. Enable Gradle dependency in Unity project

I am assuming here that everyone who is here already has a unity project and knows at least basics of unity and the folder structures in unity.

Copy the aar file generate from Android studio and place it in /Assets/Plugins/Android/ folder.

Create Plugins and Android folder if not there and inside the Android folder put the aar file.

Native Android image sharing in Unity using FileProvider
Please note that I have changed the name of the aar file from “unitysharing-release.aar” to “unitysharing.aar” but it really doesn’t matter. The name of the file really does not make any difference.

Now if you remember, we added a dependency of appcompat support library in our android library’s build.gradle file. Since the FileProvider is defined in that support library of Android only, we need the same in our unity project also. To add that dependency in our unity project we need to enable the custom build.gradle file for our unity project.

Open File > Build Settings in Unity.

Switch platform to Android.

Open Player Settings and navigate to the Publishing Settings section.

Check the checkbox next to “Custom Gradle Template”. This will generate a “mainTemplate.gradle” file in the same Assets/Plugins/Android folder.

Native Android image sharing in Unity using FileProvider

6. Add Support dependency in the gradle file.

Open the newly created “mainTemplate.gradle” file in any editor.

Add the below line at the bottom of that file.
  
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:28.0.0'
}
Please note if you already using the custom gradle file, add only appcompat dependency into your dependencies section. Please do not touch the dependencies block above android block. That dependency block is for root project similar to native android.

Please be careful with this file otherwise the project will not compile. Also, do not touch anything which is auto-generated. My final mainTemplate.gradle file will look like below.

7. Change sharing intent code in the C# file

This is the final change. I am assuming that whoever is here had already read my previous post Native Android image sharing in Unity and is aware of the basic intent object we created to share.

Since last time we were creating the URI object by parsing the File object which is not allowed now, we will create the URI object using FileProvider.
  
//old code which is not allowed in Android 8 or above
//create image URI to add it to the intent
//AndroidJavaClass uriClass = new AndroidJavaClass ("android.net.Uri");
//AndroidJavaObject uriObject =
//uriClass.CallStatic <AndroidJavaObject >("parse", "file://"
// + screenShotPath);

//create file object of the screenshot captured
AndroidJavaObject fileObject = new AndroidJavaObject("java.io.File", screenShotPath);

//create FileProvider class object
AndroidJavaClass fileProviderClass = new AndroidJavaClass("android.support.v4.content.FileProvider");
object[] providerParams = new object[3];
providerParams[0] = currentActivity;
providerParams[1] = "com.agrawalsuneet.unityclient.provider";
providerParams[2] = fileObject;

//instead of parsing the uri,
//will get the uri from file using FileProvider
AndroidJavaObject uriObject = fileProviderClass.CallStatic <AndroidJavaObject >("getUriForFile", providerParams);
Please note that the object passed at position 1 in the array should be your <application id>.provider

This is the same as what we defined in the manifest file of the Android library.
  
android:authorities="${applicationId}.provider"
Crosscheck the spelling correctly.

Additionally, add the granting permission to read the URI as a flag to the intent.
  
//additionally grant permission to read the uri
intentObject.Call <AndroidJavaObject >("addFlags", intentClass.GetStatic <int >("FLAG_GRANT_READ_URI_PERMISSION") );
The final script will look like below.

Connect this script to any game object and assign the Button and we are done.

Native Android image sharing in Unity using FileProvider
Native Android image sharing in Unity using FileProvider
I have uploaded all the code (Android and Unity both) into a public Github repository UnitySharingCenter.

I have also kept my “aar” public which can be directly used in any Unity project and can skip the above 4 steps. Please find the aar at UnitySharingCenter/Releases

Thank you for your patience. Please write in comments sections if it worked fine for you or not.