I find once Microsoft Excel
edit an excel file and saved, my app can no longer access that xlsx file by ContentResolver
on Android 14.
I am creating a .Net8 MAUI project targeting Android.
I set minSdkVersion
as 29, compileSdkVersion
and targetSdkVersion
as 34.
I want to save an excel file to the Download folder in external storage, so I asked READ_EXTERNAL_STORAGE
WRITE_EXTERNAL_STORAGE
and MANAGE_EXTERNAL_STORAGE
permissions in AndroidManifest.xml
.
Here is my sample code.
// When CREATE button clicked.
private async void OnCreateClicked(object sender, EventArgs e) {
#if ANDROID29_0_OR_GREATER
var activity = Platform.CurrentActivity!;
var contentResolver = activity.ContentResolver!;
// Delete file when existed.
var cursor = contentResolver.Query(MediaStore.Downloads.ExternalContentUri,
[IBaseColumns.Id, MediaStore.IMediaColumns.DisplayName, MediaStore.IMediaColumns.Data],
MediaStore.IMediaColumns.DisplayName + " = ?",
["myexcel.xlsx"],
null);
if (cursor?.MoveToFirst() == true) {
var id = cursor.GetLong(cursor.GetColumnIndex(IBaseColumns.Id));
var uri = ContentUris.WithAppendedId(MediaStore.Downloads.ExternalContentUri, id);
contentResolver.Delete(uri, null, null);
}
// Read xlsx file from Assets in "Resources/Raw/myexcel.xlsx"
using var stream = await FileSystem.OpenAppPackageFileAsync("myexcel.xlsx");
using var memory = new MemoryStream();
stream.CopyTo(memory);
var myexcel = memory.ToArray();
var extType = MimeTypeMap.Singleton!.GetMimeTypeFromExtension("xlsx");
var storedValue = new ContentValues();
storedValue.Put(MediaStore.IMediaColumns.DisplayName, "myexcel.xlsx");
storedValue.Put(MediaStore.IMediaColumns.MimeType, extType);
storedValue.Put(MediaStore.IMediaColumns.RelativePath, Android.OS.Environment.DirectoryDownloads + "/myexcel/");
// Save excel file with ContentResolver
var fileUri = contentResolver.Insert(MediaStore.Downloads.ExternalContentUri, storedValue)!;
using var writeStream = contentResolver.OpenOutputStream(fileUri, "rwt")!;
writeStream.Write(myexcel);
Toast.MakeText(activity, "File created.", ToastLength.Long)!.Show();
#endif
}
// When OPEN button clicked.
private void OnOpenClicked(object sender, EventArgs e) {
#if ANDROID29_0_OR_GREATER
var activity = Platform.CurrentActivity!;
var contentResolver = activity.ContentResolver!;
#if ANDROID30_0_OR_GREATER
// Make sure the app can access all files.
// After permission granted, need to click open button again.
if (!Android.OS.Environment.IsExternalStorageManager) {
try {
var packageUri = Android.Net.Uri.Parse($"package:{activity.PackageName}");
var intent = new Intent(Settings.ActionManageAppAllFilesAccessPermission, packageUri);
activity.StartActivity(intent);
} catch (Exception ex) {
Toast.MakeText(activity, ex.Message, ToastLength.Long)!.Show();
}
}
#endif
var cursor = contentResolver.Query(MediaStore.Downloads.ExternalContentUri,
[IBaseColumns.Id, MediaStore.IMediaColumns.DisplayName, MediaStore.IMediaColumns.Data],
MediaStore.IMediaColumns.DisplayName + " = ?",
["myexcel.xlsx"],
null);
if (cursor?.MoveToFirst() == true) {
// Can get in here before MSExcel editing and saving that xlsx file.
var id = cursor.GetLong(cursor.GetColumnIndex(IBaseColumns.Id));
var path = cursor.GetString(cursor.GetColumnIndex(MediaStore.IMediaColumns.Data));
var uri = ContentUris.WithAppendedId(MediaStore.Downloads.ExternalContentUri, id);
var intent = new Intent(Intent.ActionView, uri);
intent.AddFlags(ActivityFlags.NewTask)
.AddFlags(ActivityFlags.ClearTop)
.AddFlags(ActivityFlags.GrantReadUriPermission)
.AddFlags(ActivityFlags.GrantWriteUriPermission);
try {
activity.StartActivity(intent);
} catch (Exception ex) {
Toast.MakeText(activity, ex.Message, ToastLength.Long)!.Show();
}
} else {
// After MSExcel editing and saving that xlsx file, it always get in here
Toast.MakeText(activity, "File not found.", ToastLength.Long)!.Show();
}
#endif
}
I first clicked CREATE button to create an xlsx file in Download folder.
Then I clicked OPEN button to open this file using Microsoft Excel
.
If I just push back button without editing this xlsx file, the Microsoft Excel
will not do file saving progress, so I can click OPEN button in my app to open this file again.
But if I edit something using Microsoft Excel
and push back button, the Microsoft Excel
will do file saving process and I can NEVER open that xlsx file using my app as it seems like dispeared.
Some wired point:
Files
APP. If I click this file in Files
, I can open it, but that is not what I am expecting.Microsoft Excel
, I can find that xlsx file with content query
command using adb
.adb shell
content query --projection _id:_data:_display_name:owner_package_name:volume_name --uri content://media/external/downloads --where "_display_name='myexcel.xlsx'"
Row: 0 _id=1000000787, _data=/storage/emulated/0/Download/myexcel/myexcel.xlsx, _display_name=myexcel.xlsx, owner_package_name=com.companyname.filedownloadexcel, volume_name=external_primary
adb
's content query
.adb shell
content query --projection _id:_data:_display_name:owner_package_name:volume_name --uri content://media/external/downloads --where "_display_name='myexcel.xlsx'"
No result found.
Files
APP using my eyes!Files
to somename, and rename it back, I can find it using adb
and in my app by click OPEN button!stat
command using adb shell
before and after Microsoft Excel
saving xlsx file and did not find strange things in permissions.What I am expecting
Microsoft Excel
, edit something and push Android back button to save & exit.Microsoft Excel
, showing what I edited just before.ContentResolver#Delete
or somehow to delete that xlsx file after Microsoft Excel
saving it.I can see how this can happen, Excel may modify media store metadata (or) delete the original file and create a new one during the saving process. It's hard to say what may happen with any external app like Excel.
Ultimately, the file you saved to Downloads will become inaccessible using it's original URI inMediaStore
.
What you can do is have a fallback once the ContentResolver
couldn't find your excel file, by fetching it by it's absolute path.
Android.Net.Uri? fileUri = null;
if (cursor?.MoveToFirst() == true)
{
...
fileUri = ContentUris.WithAppendedId(MediaStore.Downloads.ExternalContentUri, id);
}
else
{
//fallback
var directPath = Path.Combine(Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads).AbsolutePath, fileName);
fileUri = Android.Net.Uri.FromFile(new Java.IO.File(directPath));
}
// start your intent with the fileuri