Background
So far, there has been an easy way to install the APK file using this intention:
final Intent intent=new Intent(Intent.ACTION_VIEW) .setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
But if your application is designed for Android API 24 and higher (Nougat - 7.0), and you run this code on it or newer, you will get an exception, as shown here , for example:
android.os.FileUriExposedException: file:///storage/emulated/0/sample.apk exposed beyond app through Intent.getData()
Problem
So, I did what I was told: use the FileProvider class of the support library as such:
final Intent intent = new Intent(Intent.ACTION_VIEW)// .setDataAndType(android.support.v4.content.FileProvider.getUriForFile(context, context.getPackageName() + ".provider", apkFile), "application/vnd.android.package-archive").addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
manifest
<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>
res / xml / provider_paths.xml :
<?xml version="1.0" encoding="utf-8"?> <paths> <external-path name="files_root" path="Android/data/${applicationId}"/> <external-path name="external_storage_root" path="."/> </paths>
But now it only works on Android Nougat. On Android 5.0, it throws an exception: ActivityNotFoundException.
What i tried
I can just add verification for the Android OS version and use both methods, but as I read, one method should be used: FileProvider.
So, I tried to use my own ContentProvider, which acts like a FileProvider, but I got the same exception as in the FileProvider support library.
Here is my code for it:
final Intent intent = new Intent(Intent.ACTION_VIEW) .setDataAndType(OpenFileProvider.prepareSingleFileProviderFile(apkFilePath), "application/vnd.android.package-archive") .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
OpenFileProvider.java
public class OpenFileProvider extends ContentProvider { private static final String FILE_PROVIDER_AUTHORITY = "open_file_provider"; private static final String[] DEFAULT_PROJECTION = new String[]{MediaColumns.DATA, MediaColumns.DISPLAY_NAME, MediaColumns.SIZE}; public static Uri prepareSingleFileProviderFile(String filePath) { final String encodedFilePath = new String(Base64.encode(filePath.getBytes(), Base64.URL_SAFE)); final Uri uri = Uri.parse("content://" + FILE_PROVIDER_AUTHORITY + "/" + encodedFilePath); return uri; } @Override public boolean onCreate() { return true; } @Override public String getType(@NonNull Uri uri) { String fileName = getFileName(uri); if (fileName == null) return null; return MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileName); } @Override public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException { final String fileName = getFileName(uri); if (fileName == null) return null; final File file = new File(fileName); return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); } @Override public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { final String filePath = getFileName(uri); if (filePath == null) return null; final String[] columnNames = (projection == null) ? DEFAULT_PROJECTION : projection; final MatrixCursor ret = new MatrixCursor(columnNames); final Object[] values = new Object[columnNames.length]; for (int i = 0, count = columnNames.length; i < count; ++i) { String column = columnNames[i]; switch (column) { case MediaColumns.DATA: values[i] = uri.toString(); break; case MediaColumns.DISPLAY_NAME: values[i] = extractFileName(uri); break; case MediaColumns.SIZE: File file = new File(filePath); values[i] = file.length(); break; } } ret.addRow(values); return ret; } private static String getFileName(Uri uri) { String path = uri.getLastPathSegment(); return path != null ? new String(Base64.decode(path, Base64.URL_SAFE)) : null; } private static String extractFileName(Uri uri) { String path = getFileName(uri); return path; } @Override public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0;
manifest
<provider android:name=".utils.apps_utils.OpenFileProvider" android:authorities="open_file_provider" android:exported="true" android:grantUriPermissions="true" android:multiprocess="true"/>
Questions
Why is this happening?
Is there something wrong with the user provider I created? Do I need a flag? Is a URI created? Should I add the current application package name to it?
If I just add a check if it is Android API 24 and above, and if so, use a provider, and if not, use a regular Uri.fromFile call? If I use this, the support library actually loses its purpose, because it will be used for new versions of Android ...
Will the FileProvider support library suffice for all use cases (given that I have external storage permissions, of course)?