Browse Source

Android - Query date modified for android storage folder (#20860)

* query date modified for android storage folder

* fix comments

* Update src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
pull/20953/head
Emmanuel Hansen 5 days ago
committed by GitHub
parent
commit
14b591b488
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 119
      src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs

119
src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs

@ -12,6 +12,7 @@ using Avalonia.Logging;
using Avalonia.Platform.Storage; using Avalonia.Platform.Storage;
using Avalonia.Platform.Storage.FileIO; using Avalonia.Platform.Storage.FileIO;
using Java.Lang; using Java.Lang;
using static Android.Provider.DocumentsContract;
using AndroidUri = Android.Net.Uri; using AndroidUri = Android.Net.Uri;
using Exception = System.Exception; using Exception = System.Exception;
using JavaFile = Java.IO.File; using JavaFile = Java.IO.File;
@ -35,10 +36,10 @@ internal abstract class AndroidStorageItem : IStorageBookmarkItem
} }
internal AndroidUri Uri { get; set; } internal AndroidUri Uri { get; set; }
protected Activity Activity => _activity ?? throw new ObjectDisposedException(nameof(AndroidStorageItem)); protected Activity Activity => _activity ?? throw new ObjectDisposedException(nameof(AndroidStorageItem));
public virtual string Name => GetColumnValue(Activity, Uri, DocumentsContract.Document.ColumnDisplayName) public virtual string Name => GetColumnValue(Activity, Uri, Document.ColumnDisplayName)
?? GetColumnValue(Activity, Uri, MediaStore.IMediaColumns.DisplayName) ?? GetColumnValue(Activity, Uri, MediaStore.IMediaColumns.DisplayName)
?? Uri.PathSegments?.LastOrDefault()?.Split("/", StringSplitOptions.RemoveEmptyEntries).LastOrDefault() ?? string.Empty; ?? Uri.PathSegments?.LastOrDefault()?.Split("/", StringSplitOptions.RemoveEmptyEntries).LastOrDefault() ?? string.Empty;
@ -67,7 +68,7 @@ internal abstract class AndroidStorageItem : IStorageBookmarkItem
Activity.ContentResolver?.ReleasePersistableUriPermission(Uri, ActivityFlags.GrantWriteUriPermission | ActivityFlags.GrantReadUriPermission); Activity.ContentResolver?.ReleasePersistableUriPermission(Uri, ActivityFlags.GrantWriteUriPermission | ActivityFlags.GrantReadUriPermission);
} }
public abstract Task<StorageItemProperties> GetBasicPropertiesAsync(); public abstract Task<StorageItemProperties> GetBasicPropertiesAsync();
protected static string? GetColumnValue(Context context, AndroidUri contentUri, string column, string? selection = null, string[]? selectionArgs = null) protected static string? GetColumnValue(Context context, AndroidUri contentUri, string column, string? selection = null, string[]? selectionArgs = null)
@ -98,7 +99,7 @@ internal abstract class AndroidStorageItem : IStorageBookmarkItem
return null; return null;
} }
if(_parent != null) if (_parent != null)
{ {
return _parent; return _parent;
} }
@ -106,8 +107,8 @@ internal abstract class AndroidStorageItem : IStorageBookmarkItem
using var javaFile = new JavaFile(Uri.Path!); using var javaFile = new JavaFile(Uri.Path!);
// Java file represents files AND directories. Don't be confused. // Java file represents files AND directories. Don't be confused.
if (javaFile.ParentFile is {} parentFile if (javaFile.ParentFile is { } parentFile
&& AndroidUri.FromFile(parentFile) is {} androidUri) && AndroidUri.FromFile(parentFile) is { } androidUri)
{ {
return new AndroidStorageFolder(Activity, androidUri, false); return new AndroidStorageFolder(Activity, androidUri, false);
} }
@ -124,12 +125,12 @@ internal abstract class AndroidStorageItem : IStorageBookmarkItem
return await _activity!.CheckPermission(Manifest.Permission.ReadExternalStorage); return await _activity!.CheckPermission(Manifest.Permission.ReadExternalStorage);
} }
public void Dispose() public void Dispose()
{ {
_activity = null; _activity = null;
} }
internal AndroidUri? PermissionRoot => _permissionRoot; internal AndroidUri? PermissionRoot => _permissionRoot;
public abstract Task DeleteAsync(); public abstract Task DeleteAsync();
@ -138,8 +139,8 @@ internal abstract class AndroidStorageItem : IStorageBookmarkItem
public static IStorageItem CreateItem(Activity activity, AndroidUri uri) public static IStorageItem CreateItem(Activity activity, AndroidUri uri)
{ {
var mimeType = GetColumnValue(activity, uri, DocumentsContract.Document.ColumnMimeType); var mimeType = GetColumnValue(activity, uri, Document.ColumnMimeType);
if (mimeType == DocumentsContract.Document.MimeTypeDir) if (mimeType == Document.MimeTypeDir)
{ {
return new AndroidStorageFolder(activity, uri, false); return new AndroidStorageFolder(activity, uri, false);
} }
@ -160,8 +161,8 @@ internal class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
{ {
var mimeType = MimeTypeMap.Singleton?.GetMimeTypeFromExtension(MimeTypeMap.GetFileExtensionFromUrl(name)) ?? "application/octet-stream"; var mimeType = MimeTypeMap.Singleton?.GetMimeTypeFromExtension(MimeTypeMap.GetFileExtensionFromUrl(name)) ?? "application/octet-stream";
var treeUri = GetTreeUri().treeUri; var treeUri = GetTreeUri().treeUri;
var newFile = DocumentsContract.CreateDocument(Activity.ContentResolver!, treeUri!, mimeType, name); var newFile = CreateDocument(Activity.ContentResolver!, treeUri!, mimeType, name);
if(newFile == null) if (newFile == null)
{ {
return Task.FromResult<IStorageFile?>(null); return Task.FromResult<IStorageFile?>(null);
} }
@ -172,7 +173,7 @@ internal class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
public Task<IStorageFolder?> CreateFolderAsync(string name) public Task<IStorageFolder?> CreateFolderAsync(string name)
{ {
var treeUri = GetTreeUri().treeUri; var treeUri = GetTreeUri().treeUri;
var newFolder = DocumentsContract.CreateDocument(Activity.ContentResolver!, treeUri!, DocumentsContract.Document.MimeTypeDir, name); var newFolder = CreateDocument(Activity.ContentResolver!, treeUri!, Document.MimeTypeDir, name);
if (newFolder == null) if (newFolder == null)
{ {
return Task.FromResult<IStorageFolder?>(null); return Task.FromResult<IStorageFolder?>(null);
@ -197,24 +198,76 @@ internal class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
{ {
await foreach (var file in storageFolder.GetItemsAsync()) await foreach (var file in storageFolder.GetItemsAsync())
{ {
if(file is AndroidStorageFolder folder) if (file is AndroidStorageFolder folder)
{ {
await DeleteContents(folder); await DeleteContents(folder);
} }
else if(file is AndroidStorageFile storageFile) else if (file is AndroidStorageFile storageFile)
{ {
await storageFile.DeleteAsync(); await storageFile.DeleteAsync();
} }
} }
var treeUri = GetTreeUri().treeUri; var treeUri = GetTreeUri().treeUri;
DocumentsContract.DeleteDocument(Activity.ContentResolver!, treeUri!); DeleteDocument(Activity.ContentResolver!, treeUri!);
} }
} }
public override Task<StorageItemProperties> GetBasicPropertiesAsync() public override Task<StorageItemProperties> GetBasicPropertiesAsync()
{ {
return Task.FromResult(new StorageItemProperties()); DateTimeOffset? dateModified = null;
AndroidUri? queryUri = null;
try
{
try
{
// When Uri is a tree URI, use its document id to build a document URI.
var folderId = GetTreeDocumentId(Uri);
queryUri = BuildDocumentUriUsingTree(Uri, folderId);
}
catch (UnsupportedOperationException)
{
// For non-root items, Uri may already be a document URI; use it directly.
queryUri = Uri;
}
if (queryUri != null)
{
var projection = new[]
{
Document.ColumnLastModified
};
using var cursor = Activity.ContentResolver!.Query(queryUri, projection, null, null, null);
if (cursor?.MoveToFirst() == true)
{
try
{
var columnIndex = cursor.GetColumnIndex(Document.ColumnLastModified);
if (columnIndex != -1)
{
var longValue = cursor.GetLong(columnIndex);
dateModified = longValue > 0 ? DateTimeOffset.FromUnixTimeMilliseconds(longValue) : null;
}
}
catch (Exception ex)
{
Logger.TryGet(LogEventLevel.Verbose, LogArea.AndroidPlatform)?
.Log(this, "Directory LastModified metadata reader failed: '{Exception}'", ex);
}
}
}
}
catch (Exception ex)
{
// Data may not be available for this item or the URI may not be in the expected shape.
Logger.TryGet(LogEventLevel.Verbose, LogArea.AndroidPlatform)?
.Log(this, "Directory basic properties metadata unavailable: '{Exception}'", ex);
}
return Task.FromResult(new StorageItemProperties(null, null, dateModified));
} }
public async IAsyncEnumerable<IStorageItem> GetItemsAsync() public async IAsyncEnumerable<IStorageItem> GetItemsAsync()
@ -234,8 +287,8 @@ internal class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
var projection = new[] var projection = new[]
{ {
DocumentsContract.Document.ColumnDocumentId, Document.ColumnDocumentId,
DocumentsContract.Document.ColumnMimeType Document.ColumnMimeType
}; };
if (childrenUri != null) if (childrenUri != null)
{ {
@ -247,8 +300,8 @@ internal class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
var mime = cursor.GetString(1); var mime = cursor.GetString(1);
var id = cursor.GetString(0); var id = cursor.GetString(0);
bool isDirectory = mime == DocumentsContract.Document.MimeTypeDir; bool isDirectory = mime == Document.MimeTypeDir;
var uri = DocumentsContract.BuildDocumentUriUsingTree(root, id); var uri = BuildDocumentUriUsingTree(root, id);
if (uri == null) if (uri == null)
{ {
@ -313,9 +366,9 @@ internal class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
var projection = new[] var projection = new[]
{ {
DocumentsContract.Document.ColumnDocumentId, Document.ColumnDocumentId,
DocumentsContract.Document.ColumnMimeType, Document.ColumnMimeType,
DocumentsContract.Document.ColumnDisplayName Document.ColumnDisplayName
}; };
if (childrenUri != null) if (childrenUri != null)
@ -332,15 +385,15 @@ internal class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
if (fileName != name) if (fileName != name)
{ {
continue; continue;
} }
bool mineDirectory = mime == DocumentsContract.Document.MimeTypeDir; bool mineDirectory = mime == Document.MimeTypeDir;
if (isDirectory != mineDirectory) if (isDirectory != mineDirectory)
{ {
return null; return null;
} }
var uri = DocumentsContract.BuildDocumentUriUsingTree(root, id); var uri = BuildDocumentUriUsingTree(root, id);
if (uri == null) if (uri == null)
{ {
return null; return null;
@ -370,8 +423,8 @@ internal class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
private (AndroidUri root, AndroidUri? treeUri) GetTreeUri() private (AndroidUri root, AndroidUri? treeUri) GetTreeUri()
{ {
var root = PermissionRoot ?? Uri; var root = PermissionRoot ?? Uri;
var folderId = root != Uri ? DocumentsContract.GetDocumentId(Uri) : DocumentsContract.GetTreeDocumentId(Uri); var folderId = root != Uri ? GetDocumentId(Uri) : GetTreeDocumentId(Uri);
return (root, DocumentsContract.BuildChildDocumentsUriUsingTree(root, folderId)); return (root, BuildChildDocumentsUriUsingTree(root, folderId));
} }
} }
@ -419,10 +472,10 @@ internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkF
if (!OperatingSystem.IsAndroidVersionAtLeast(24)) if (!OperatingSystem.IsAndroidVersionAtLeast(24))
return false; return false;
if (!DocumentsContract.IsDocumentUri(context, uri)) if (!IsDocumentUri(context, uri))
return false; return false;
var value = GetColumnValue(context, uri, DocumentsContract.Document.ColumnFlags); var value = GetColumnValue(context, uri, Document.ColumnFlags);
if (!string.IsNullOrEmpty(value) && int.TryParse(value, out var flagsInt)) if (!string.IsNullOrEmpty(value) && int.TryParse(value, out var flagsInt))
{ {
var flags = (DocumentContractFlags)flagsInt; var flags = (DocumentContractFlags)flagsInt;
@ -530,7 +583,7 @@ internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkF
if (Activity != null) if (Activity != null)
{ {
DocumentsContract.DeleteDocument(Activity.ContentResolver!, Uri); DeleteDocument(Activity.ContentResolver!, Uri);
} }
} }
@ -553,7 +606,7 @@ internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkF
storageFolder.Uri is { } targetParentUri && storageFolder.Uri is { } targetParentUri &&
await GetParentAsync() is AndroidStorageFolder parentFolder) await GetParentAsync() is AndroidStorageFolder parentFolder)
{ {
movedUri = DocumentsContract.MoveDocument(contentResolver, Uri, parentFolder.Uri, targetParentUri); movedUri = MoveDocument(contentResolver, Uri, parentFolder.Uri, targetParentUri);
} }
} }
catch (Exception) catch (Exception)

Loading…
Cancel
Save