66using OwlCore . Storage ;
77using SecureFolderFS . Core . MobileFS . Platforms . Android . Streams ;
88using SecureFolderFS . Maui . Platforms . Android . Storage . StorageProperties ;
9+ using SecureFolderFS . Shared . Helpers ;
910using SecureFolderFS . Storage . StorageProperties ;
1011using AndroidUri = Android . Net . Uri ;
1112
@@ -26,11 +27,11 @@ internal sealed class AndroidFile : AndroidStorable, IChildFile, ILastModifiedAt
2627 /// <inheritdoc/>
2728 public ISizeOfProperty SizeOf => field ??= new AndroidSizeOfProperty ( Id , Document ?? throw new ArgumentNullException ( nameof ( Document ) ) ) ;
2829
29- public AndroidFile ( AndroidUri uri , Activity activity , AndroidFolder ? parent = null , AndroidUri ? permissionRoot = null , string ? bookmarkId = null )
30+ public AndroidFile ( AndroidUri uri , Activity activity , AndroidFolder ? parent = null , AndroidUri ? permissionRoot = null , string ? bookmarkId = null , string ? name = null )
3031 : base ( uri , activity , parent , permissionRoot , bookmarkId )
3132 {
3233 Document = DocumentFile . FromSingleUri ( activity , uri ) ;
33- Name = Document ? . Name ?? base . Name ;
34+ Name = name ?? Document ? . Name ?? base . Name ;
3435 }
3536
3637 /// <inheritdoc/>
@@ -47,6 +48,20 @@ public Task<Stream> OpenStreamAsync(FileAccess accessMode, CancellationToken can
4748
4849 if ( accessMode == FileAccess . Read )
4950 {
51+ // Prefer a file-descriptor-backed stream which is seekable - random access
52+ // is required both by the crypto layer and by consumers seeking within files
53+ var readFd = SafetyHelpers . NoFailureResult ( ( ) => activity . ContentResolver ? . OpenFileDescriptor ( Inner , "r" ) ) ;
54+ if ( readFd is not null )
55+ {
56+ var readChannel = new FileInputStream ( readFd . FileDescriptor ) . Channel ;
57+ if ( readChannel is not null )
58+ return Task . FromResult < Stream > ( new ChannelledStream ( readChannel , null , readFd ) ) ;
59+
60+ readFd . Close ( ) ;
61+ }
62+
63+ // Fall back to a plain (forward-only) input stream for providers
64+ // that cannot supply a seekable file descriptor
5065 var inStream = activity . ContentResolver ? . OpenInputStream ( Inner ) ;
5166 if ( inStream is null )
5267 return Task . FromException < Stream > ( new UnauthorizedAccessException ( "Could not open input stream." ) ) ;
@@ -55,7 +70,9 @@ public Task<Stream> OpenStreamAsync(FileAccess accessMode, CancellationToken can
5570 }
5671 else
5772 {
58- var fd = activity . ContentResolver ? . OpenFileDescriptor ( Inner , "rwt" ) ;
73+ // Open in "rw" mode - "rwt" would truncate the existing content on open.
74+ // Truncation is a deliberate operation performed by the caller, never a side effect
75+ var fd = activity . ContentResolver ? . OpenFileDescriptor ( Inner , "rw" ) ;
5976 if ( fd is null )
6077 return Task . FromException < Stream > ( new UnauthorizedAccessException ( "Could not open file descriptor." ) ) ;
6178
0 commit comments