This repository has been archived by the owner on May 15, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 500
GH-130 & GH-129: Android support for safe shareable file URI’s #416
Merged
Merged
Changes from 44 commits
Commits
Show all changes
47 commits
Select commit
Hold shift + click to select a range
d330940
Android: Support for safe shareable file URI’s
Redth b377ce5
Fix absolute type naming
Redth 6695cb9
Add a user interaction test for File Provider
Redth f40ce30
Fix vibration code
Redth 3147070
Reorder using statements
Redth e2efa96
Fix test attribute
Redth ef1be9d
Get provider authority properly
Redth 333d0a0
Added external storage permission
Redth 5d2d142
Change file provider path
Redth 4ba6735
Copy file into temp folder instead of file
Redth 3281aaa
Resgen
Redth 9413620
Merge branch 'master' into feature/android-file-provider
Redth 036bbc1
Merge branch 'master' into feature/android-file-provider
Redth 1f6d48d
Merge branch 'master' into feature/android-file-provider
Redth f3a22cc
Merge branch 'master' into feature/android-file-provider
mattleibow ec7ca41
Merge branch 'master' into feature/android-file-provider
jamesmontemagno 5c5b3f2
Permissions may need to be checked to control functionality
mattleibow 652c6a7
The Android FileProvider now can detect permissions
mattleibow 8abd65e
Added support for email attachments
mattleibow f587716
Added attachments to the sample app
mattleibow 8df5c98
Updated the docs with the new types
mattleibow 494d2c6
Some fixes for iOS
mattleibow ba85b64
Merge branch 'dev/1.1.0' into feature/android-file-provider
mattleibow fbf0b8f
Fix the mdoc target
mattleibow 8257a18
regen docs
mattleibow c4c2c96
remove the obsolete armeabi ABI
mattleibow 5c7b54a
Reworked the file logic to try and use public folders first
mattleibow 00a6a6f
Be more specific with the external storage permission name
mattleibow b557743
Added some more depth to the comments here
Redth a56c942
Unnecessary else
Redth b1ed40e
Added base file info class
Redth a6044b5
EmailAttachment now derives from FileBase
Redth 3561832
Added File Sharing
Redth f71f741
Keep track of IStorageFile internally
Redth 13f7896
Prefer internal IStorageFile in UWP
Redth 6c6f1e1
Use attachment name properly in UWP
Redth 267ec59
Add ctor to create from existing FileBase
Redth 880ff39
Add ctors for FileBase
Redth 14cea84
Add ctors for ShareFileRequest
Redth b8037f0
Merge branch 'dev/1.1.0' into feature/android-file-provider
mattleibow 7ceee2e
We can't use N on pre-N platforms
mattleibow 7fc76d6
Updated the docs
mattleibow 4f4ad8f
Update some docs.
Redth cf38175
Bump
Redth 4af2507
Gate Email/Share files with feature flags
Redth 2f0c268
Add sample for ShareFileRequest
Redth e5c1671
Added test for share method in netstandard
Redth File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
3,910 changes: 2,014 additions & 1,896 deletions
3,910
DeviceTests/DeviceTests.Android/Resources/Resource.designer.cs
Large diffs are not rendered by default.
Oops, something went wrong.
238 changes: 238 additions & 0 deletions
238
DeviceTests/DeviceTests.Android/Tests/FileProvider_Tests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,238 @@ | ||
using System; | ||
using System.IO; | ||
using System.Linq; | ||
using Xamarin.Essentials; | ||
using Xunit; | ||
using AndroidEnvironment = Android.OS.Environment; | ||
|
||
namespace DeviceTests.Shared | ||
{ | ||
public class Android_FileProvider_Tests | ||
{ | ||
[Fact] | ||
[Trait(Traits.InteractionType, Traits.InteractionTypes.Human)] | ||
public void Share_Simple_Text_File_Test() | ||
{ | ||
// Save a local cache data directory file | ||
var file = CreateFile(FileSystem.AppDataDirectory, "share-test.txt"); | ||
|
||
// Make sure it is where we expect it to be | ||
Assert.False(FileProvider.IsFileInPublicLocation(file)); | ||
|
||
// Actually get a safe shareable file uri | ||
var shareableUri = Platform.GetShareableFileUri(file); | ||
|
||
// Launch an intent to let tye user pick where to open this content | ||
var intent = new Android.Content.Intent(Android.Content.Intent.ActionSend); | ||
intent.SetType("text/plain"); | ||
intent.PutExtra(Android.Content.Intent.ExtraStream, shareableUri); | ||
intent.PutExtra(Android.Content.Intent.ExtraTitle, "Title Here"); | ||
intent.SetFlags(Android.Content.ActivityFlags.GrantReadUriPermission); | ||
|
||
var intentChooser = Android.Content.Intent.CreateChooser(intent, "Pick something"); | ||
|
||
Platform.AppContext.StartActivity(intentChooser); | ||
} | ||
|
||
[Theory] | ||
[InlineData(true, FileProviderLocation.Internal)] | ||
[InlineData(true, FileProviderLocation.PreferExternal)] | ||
[InlineData(false, FileProviderLocation.Internal)] | ||
[InlineData(false, FileProviderLocation.PreferExternal)] | ||
public void Get_Shareable_Uri(bool failAccess, FileProviderLocation location) | ||
{ | ||
// Always fail to simulate unmounted media | ||
FileProvider.AlwaysFailExternalMediaAccess = failAccess; | ||
|
||
try | ||
{ | ||
// Save a local cache data directory file | ||
var file = CreateFile(FileSystem.AppDataDirectory); | ||
|
||
// Make sure it is where we expect it to be | ||
Assert.False(FileProvider.IsFileInPublicLocation(file)); | ||
|
||
// Actually get a safe shareable file uri | ||
var shareableUri = GetShareableUri(file, location); | ||
|
||
// Determine where the file should be found | ||
var isInternal = (failAccess || location == FileProviderLocation.Internal); | ||
var expectedCache = isInternal ? "internal_cache" : "external_cache"; | ||
var expectedCacheDir = isInternal | ||
? Platform.AppContext.CacheDir.AbsolutePath | ||
: Platform.AppContext.ExternalCacheDir.AbsolutePath; | ||
|
||
// Make sure the uri is what we expected | ||
Assert.NotNull(shareableUri); | ||
Assert.Equal("content", shareableUri.Scheme); | ||
Assert.Equal("com.xamarin.essentials.devicetests.fileProvider", shareableUri.Authority); | ||
Assert.Equal(4, shareableUri.PathSegments.Count); | ||
Assert.Equal(expectedCache, shareableUri.PathSegments[0]); | ||
Assert.Equal("2203693cc04e0be7f4f024d5f9499e13", shareableUri.PathSegments[1]); | ||
Assert.True(Guid.TryParseExact(shareableUri.PathSegments[2], "N", out var guid)); | ||
Assert.Equal(Path.GetFileName(file), shareableUri.PathSegments[3]); | ||
|
||
// Make sure the underlying file exists | ||
var realPath = Path.Combine(shareableUri.PathSegments.ToArray()) | ||
.Replace(expectedCache, expectedCacheDir); | ||
Assert.True(File.Exists(realPath)); | ||
} | ||
finally | ||
{ | ||
FileProvider.AlwaysFailExternalMediaAccess = false; | ||
} | ||
} | ||
|
||
[Fact] | ||
public void No_Media_Fails_Get_External_Cache_Shareable_Uri() | ||
{ | ||
// Always fail to simulate unmounted media | ||
FileProvider.AlwaysFailExternalMediaAccess = true; | ||
|
||
try | ||
{ | ||
// Save a local cache data directory file | ||
var file = CreateFile(FileSystem.AppDataDirectory); | ||
|
||
// Make sure it is where we expect it to be | ||
Assert.False(FileProvider.IsFileInPublicLocation(file)); | ||
|
||
// try get a uri, but fail as there is no external storage | ||
Assert.Throws<InvalidOperationException>(() => GetShareableUri(file, FileProviderLocation.External)); | ||
} | ||
finally | ||
{ | ||
FileProvider.AlwaysFailExternalMediaAccess = false; | ||
} | ||
} | ||
|
||
[Fact] | ||
public void Get_External_Cache_Shareable_Uri() | ||
{ | ||
// Save a local cache data directory file | ||
var file = CreateFile(FileSystem.AppDataDirectory); | ||
|
||
// Make sure it is where we expect it to be | ||
Assert.False(FileProvider.IsFileInPublicLocation(file)); | ||
|
||
// Actually get a safe shareable file uri | ||
var shareableUri = GetShareableUri(file, FileProviderLocation.External); | ||
|
||
// Make sure the uri is what we expected | ||
Assert.NotNull(shareableUri); | ||
Assert.Equal("content", shareableUri.Scheme); | ||
Assert.Equal("com.xamarin.essentials.devicetests.fileProvider", shareableUri.Authority); | ||
Assert.Equal(4, shareableUri.PathSegments.Count); | ||
Assert.Equal("external_cache", shareableUri.PathSegments[0]); | ||
Assert.Equal("2203693cc04e0be7f4f024d5f9499e13", shareableUri.PathSegments[1]); | ||
Assert.True(Guid.TryParseExact(shareableUri.PathSegments[2], "N", out var guid)); | ||
Assert.Equal(Path.GetFileName(file), shareableUri.PathSegments[3]); | ||
|
||
// Make sure the underlying file exists | ||
var realPath = Path.Combine(shareableUri.PathSegments.ToArray()) | ||
.Replace("external_cache", Platform.AppContext.ExternalCacheDir.AbsolutePath); | ||
Assert.True(File.Exists(realPath)); | ||
} | ||
|
||
[Theory] | ||
[InlineData(FileProviderLocation.External)] | ||
[InlineData(FileProviderLocation.Internal)] | ||
[InlineData(FileProviderLocation.PreferExternal)] | ||
public void Get_Existing_Internal_Cache_Shareable_Uri(FileProviderLocation location) | ||
{ | ||
// Save a local cache directory file | ||
var file = CreateFile(Platform.AppContext.CacheDir.AbsolutePath); | ||
|
||
// Make sure it is where we expect it to be | ||
Assert.True(FileProvider.IsFileInPublicLocation(file)); | ||
|
||
// Actually get a safe shareable file uri | ||
var shareableUri = GetShareableUri(file, location); | ||
|
||
// Make sure the uri is what we expected | ||
Assert.NotNull(shareableUri); | ||
Assert.Equal("content", shareableUri.Scheme); | ||
Assert.Equal("com.xamarin.essentials.devicetests.fileProvider", shareableUri.Authority); | ||
Assert.Equal(new[] { "internal_cache", Path.GetFileName(file) }, shareableUri.PathSegments); | ||
} | ||
|
||
[Theory] | ||
[InlineData(FileProviderLocation.External)] | ||
[InlineData(FileProviderLocation.Internal)] | ||
[InlineData(FileProviderLocation.PreferExternal)] | ||
public void Get_Existing_External_Cache_Shareable_Uri(FileProviderLocation location) | ||
{ | ||
// Save an external cache directory file | ||
var file = CreateFile(Platform.AppContext.ExternalCacheDir.AbsolutePath); | ||
|
||
// Make sure it is where we expect it to be | ||
Assert.True(FileProvider.IsFileInPublicLocation(file)); | ||
|
||
// Actually get a safe shareable file uri | ||
var shareableUri = GetShareableUri(file, location); | ||
|
||
// Make sure the uri is what we expected | ||
Assert.NotNull(shareableUri); | ||
Assert.Equal("content", shareableUri.Scheme); | ||
Assert.Equal("com.xamarin.essentials.devicetests.fileProvider", shareableUri.Authority); | ||
Assert.Equal(new[] { "external_cache", Path.GetFileName(file) }, shareableUri.PathSegments); | ||
} | ||
|
||
[Theory] | ||
[InlineData(FileProviderLocation.External)] | ||
[InlineData(FileProviderLocation.Internal)] | ||
[InlineData(FileProviderLocation.PreferExternal)] | ||
public void Get_Existing_External_Shareable_Uri(FileProviderLocation location) | ||
{ | ||
// Save an external directory file | ||
var externalRoot = AndroidEnvironment.ExternalStorageDirectory.AbsolutePath; | ||
var root = Platform.AppContext.GetExternalFilesDir(null).AbsolutePath; | ||
var file = CreateFile(root); | ||
|
||
// Make sure it is where we expect it to be | ||
Assert.True(FileProvider.IsFileInPublicLocation(file)); | ||
|
||
// Actually get a safe shareable file uri | ||
var shareableUri = GetShareableUri(file, location); | ||
|
||
// Make sure the uri is what we expected | ||
Assert.NotNull(shareableUri); | ||
Assert.Equal("content", shareableUri.Scheme); | ||
Assert.Equal("com.xamarin.essentials.devicetests.fileProvider", shareableUri.Authority); | ||
|
||
// replace the real root with the providers "root" | ||
var segements = Path.Combine(root.Replace(externalRoot, "external_files"), Path.GetFileName(file)); | ||
|
||
Assert.Equal(segements.Split(Path.DirectorySeparatorChar), shareableUri.PathSegments); | ||
} | ||
|
||
static string CreateFile(string root, string name = "the-file.txt") | ||
{ | ||
var file = Path.Combine(root, name); | ||
|
||
if (File.Exists(file)) | ||
File.Delete(file); | ||
|
||
File.WriteAllText(file, "The file contents."); | ||
|
||
return file; | ||
} | ||
|
||
static Android.Net.Uri GetShareableUri(string file, FileProviderLocation location) | ||
{ | ||
try | ||
{ | ||
// use the specific location | ||
FileProvider.TemporaryLocation = location; | ||
|
||
// get the uri | ||
return Platform.GetShareableFileUri(file); | ||
} | ||
finally | ||
{ | ||
// reset the location | ||
FileProvider.TemporaryLocation = FileProviderLocation.PreferExternal; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Android has a set of this-platform-only tests, so we just add it to the app