How to open apk file for all versions of Android

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="external_files" path="."/>--> <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; // not supported } @Override public int delete(@NonNull Uri uri, String arg1, String[] arg2) { return 0; // not supported } @Override public Uri insert(@NonNull Uri uri, ContentValues values) { return null; // not supported } } 

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)?

+5
android android-intent android-7.0-nougat apk android-fileprovider
Dec 15 '16 at 9:15
source share
2 answers

I can simply add verification of the Android OS version and use any of these methods, but, as I already read, one method should be used: FileProvider.

Well, as they say, "two are needed for tango."

To use any particular scheme ( file , content , http , etc.), you need to not only provide data in this scheme, but the recipient must be able to support the reception of data in this scheme.

In the case of the package installer, content support in the form of a scheme was added only in Android 7.0 (and that, perhaps, only because I pointed out the problem ).

Why is this happening?

Because Google (see This and this ).

Is there something wrong with the user-created provider?

Probably no.

Is it worth it to simply 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?

Yes. Or, if you prefer, catch an ActivityNotFoundException and respond to it, or use the PackageManager and resolveActivity() to see ahead of time if the given Intent (for example, one with content Uri ) will work properly.

If I use this, the support library actually loses its purpose, because it will be used for newer versions of Android

The “support library” has nothing to do with the newer versions of -older for Android. Only a small percentage of classes among the various Android support artifacts are backports or compatibility. FileProvider huge amount - FileProvider , ViewPager , ConstraintLayout , etc. - these are just classes that Google wanted to provide and maintain, but wanted to make them available outside of the embedded software.

Will the FileProvider library support be sufficient for all use cases?

Only on Android 7. 0+. Again, the standard Android package installer does not support content schemes prior to Android 7.0.

+5
Dec 15 '16 at 13:02
source share

just for those who are interested in how to properly install the APK, here:

 @JvmStatic fun prepareAppInstallationIntent(context: Context, file: File, requestResult: Boolean): Intent? { var intent: Intent? = null try { intent = Intent(Intent.ACTION_INSTALL_PACKAGE)// .setDataAndType( if (VERSION.SDK_INT >= VERSION_CODES.N) androidx.core.content.FileProvider.getUriForFile(context, context.packageName + ".provider", file) else Uri.fromFile(file), "application/vnd.android.package-archive") .putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true) .putExtra(Intent.EXTRA_RETURN_RESULT, requestResult) .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) if (VERSION.SDK_INT < VERSION_CODES.JELLY_BEAN) intent!!.putExtra(Intent.EXTRA_ALLOW_REPLACE, true) } catch (e: Throwable) { } return intent } 

manifesto

 <provider android:name="androidx.core.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="external_files" path="."/>--> <external-path name="files_root" path="Android/data/${applicationId}"/> <external-path name="external_storage_root" path="."/> </paths> 
0
Apr 13 '19 at 18:59
source share



All Articles