Je dois réparer notre application pour Android N en raison des modifications apportées à FileProvider. Pour l’instant, j’ai tout lu sur ce sujet, mais aucune solution n’a été trouvée.
Voici notre code précédent qui commence les téléchargements depuis notre application, les stocke dans le dossier Download
et appelle une intention ACTION_VIEW
aussi vite que la DownloadManager
indique qu'il a terminé le téléchargement:
BroadcastReceiver onComplete = new BroadcastReceiver() {
public void onReceive(Context ctxt, Intent intent) {
Log.d(TAG, "Download commplete");
// Check for our download
long referenceId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
if (mDownloadReference == referenceId) {
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(mDownloadReference);
Cursor c = mDownloadManager.query(query);
if (c.moveToFirst()) {
int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) {
String localUri = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
String fileExtension = MimeTypeMap.getFileExtensionFromUrl(localUri);
String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension);
if (mimeType != null) {
Intent openFileIntent = new Intent(Intent.ACTION_VIEW);
openFileIntent.setDataAndTypeAndNormalize(Uri.parse(localUri), mimeType);
openFileIntent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
try {
mAcme.startActivity(openFileIntent);
}
catch (ActivityNotFoundException e) {
// Ignore if no activity was found.
}
}
}
}
}
}
};
Cela fonctionne sur Android M, mais rompt sur N en raison de la populaire FileUriExposedException
. J'ai maintenant essayé de résoudre ce problème en utilisant la variable FileProvider
, mais je ne parviens pas à le faire fonctionner. Cela se casse lorsque j'essaie d'obtenir l'URI du contenu:
Failed to find configured root that contains /file:/storage/emulated/0/Download/test.pdf
La localUri
renvoyée de la DownloadManager
pour le fichier est:
file:///storage/emulated/0/Download/test.pdf
La Environment.getExternalStorageDirectory()
renvoie /storage/emulated/0
et voici le code de la conversion:
File file = new File(localUri);
Log.d(TAG, localUri + " - " + Environment.getExternalStorageDirectory());
Uri contentUri = FileProvider.getUriForFile(ctxt, "my.file.provider", file);
De AndroidManifest.xml
<provider
Android:name="Android.support.v4.content.FileProvider"
Android:authorities="my.file.provider"
Android:exported="false"
Android:grantUriPermissions="true">
<meta-data
Android:name="Android.support.FILE_PROVIDER_PATHS"
Android:resource="@xml/file_paths"/>
</provider>
Le file_paths.xml
:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:Android="http://schemas.Android.com/apk/res/Android">
<external-path name="external_path" path="." />
</paths>
Et j'ai essayé toutes les valeurs que je pouvais trouver dans ce fichier XML. :(
Grâce à @greenaps, j'ai pu résoudre le problème. L'URI local extrait de DownlodManager
était précédé de file://
. Ceci doit être abandonné pour la nouvelle FileProvider
:
if (localUri.substring(0, 7).matches("file://")) {
localUri = localUri.substring(7);
}
File file = new File(localUri);
changer ici dans AndroidManifest.xml
<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/file_paths"/>
</provider>
changement de chemin du fichier
Uri contentUri;
if(Build.VERSION.SDK_INT == 24){
contentUri = FileProvider.getUriForFile(MainActivity.this,
getApplicationContext().getPackageName() + ".provider",
file);
} else{
contentUri = Uri.fromFile(file);
}
Comment obtenir le fichier chemin exact nom_fichier. périphérique inférieur et supérieur.
classe publique RealPathUtil {
public static String getRealPath(Context context, Uri fileUri) {
String realPath;
// SDK < API11
if (Build.VERSION.SDK_INT < 11) {
realPath = RealPathUtil.getRealPathFromURI_BelowAPI11(context, fileUri);
}
// SDK >= 11 && SDK < 19
else if (Build.VERSION.SDK_INT < 19) {
realPath = RealPathUtil.getRealPathFromURI_API11to18(context, fileUri);
}
// SDK > 19 (Android 4.4) and up
else {
realPath = RealPathUtil.getRealPathFromURI_API19(context, fileUri);
}
return realPath;
}
@SuppressLint("NewApi")
public static String getRealPathFromURI_API11to18(Context context, Uri contentUri) {
String[] proj = {MediaStore.Images.Media.DATA};
String result = null;
CursorLoader cursorLoader = new CursorLoader(context, contentUri, proj, null, null, null);
Cursor cursor = cursorLoader.loadInBackground();
if (cursor != null) {
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
result = cursor.getString(column_index);
cursor.close();
}
return result;
}
public static String getRealPathFromURI_BelowAPI11(Context context, Uri contentUri) {
String[] proj = {MediaStore.Images.Media.DATA};
Cursor cursor = context.getContentResolver().query(contentUri, proj, null, null, null);
int column_index = 0;
String result = "";
if (cursor != null) {
column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
result = cursor.getString(column_index);
cursor.close();
return result;
}
return result;
}
/**
* Get a file path from a Uri. This will get the the path for Storage Access
* Framework Documents, as well as the _data field for the MediaStore and
* other file-based ContentProviders.
*
* @param context The context.
* @param uri The Uri to query.
* @author paulburke
*/
@SuppressLint("NewApi")
public static String getRealPathFromURI_API19(final Context context, final Uri uri) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KitKat;
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
// TODO handle non-primary volumes
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[]{
split[1]
};
return getDataColumn(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
// Return the remote address
if (isGooglePhotosUri(uri))
return uri.getLastPathSegment();
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
/**
* Get the value of the data column for this Uri. This is useful for
* MediaStore Uris, and other file-based ContentProviders.
*
* @param context The context.
* @param uri The Uri to query.
* @param selection (Optional) Filter used in the query.
* @param selectionArgs (Optional) Selection arguments used in the query.
* @return The value of the _data column, which is typically a file path.
*/
public static String getDataColumn(Context context, Uri uri, String selection,
String[] selectionArgs) {
Cursor cursor = null;
final String column = "_data";
final String[] projection = {
column
};
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
null);
if (cursor != null && cursor.moveToFirst()) {
final int index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(index);
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
public static boolean isExternalStorageDocument(Uri uri) {
return "com.Android.externalstorage.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
public static boolean isDownloadsDocument(Uri uri) {
return "com.Android.providers.downloads.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
public static boolean isMediaDocument(Uri uri) {
return "com.Android.providers.media.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is Google Photos.
*/
public static boolean isGooglePhotosUri(Uri uri) {
return "com.google.Android.apps.photos.content".equals(uri.getAuthority());
}
}
C'est la solution exacte. Appelez simplement Uri selectedResume = data.getData (); String mediaResumePath = RealPathUtil.getRealPath (this, selectedResume);
J'avais un problème similaire et je l'ai résolu en n'ouvrant pas automatiquement le fichier, mais en affichant la notification "Téléchargement terminé", puis en laissant le système Android ouvrir le fichier lorsque l'utilisateur clique sur la notification.
Pour informer l'utilisateur de plus, je montre un Toast lorsque le téléchargement est terminé.
DownloadManager mManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
String url = "your URL";
String filename = "file.pdf";
// Set up the request.
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url))
.setTitle("Test")
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename)
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
.setDescription("Downloading...")
.setMimeType("application/pdf");
request.allowScanningByMediaScanner();
mManager.enqueue(request);
BroadcastReceiver:
public class DownloadReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
case DownloadManager.ACTION_DOWNLOAD_COMPLETE:
Toast.makeText(context, "Download completed", Toast.LENGTH_SHORT).show();
break;
}
}
}
Peut-être cette solution plus claire?
Uri uriParser = Uri.parse(downloadedPackageUriString);
File downloadedFile = new File(uriParser.getPath());
avant
file: ///storage/emulated/0/Download/ZS%20Gac%C3%ADkov%C3%A1.pdf
après
/ stockage/émulé/0/Télécharger/ZS Gacíková.pdf