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 501
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* Android: Support for safe shareable file URI’s On later versions of Android, you have to wrap streams of data you want to share outside your app (between apps) in a stream through a content provider. Android Support providers a general use FileProvider we can use for this. This commit basically adds support for getting all the right AndroidManifest declarations for the custom file provider based on the android support provider, so that we expose an internal method which gets a URI safe for sharing outside of the app. * Fix absolute type naming * Add a user interaction test for File Provider * Fix vibration code the ifdef meant an empty `else { }` statement with no `if { }` for platforms < 26. This fixes that. * Reorder using statements * Fix test attribute * Get provider authority properly * Added external storage permission * Change file provider path This is md5(“xamarin_essentials”) * Copy file into temp folder instead of file We keep the filename the same this way but use a GUID for a temp sub-folder to ensure a unique path. * Resgen * Permissions may need to be checked to control functionality * The Android FileProvider now can detect permissions - internal / external storage can be controlled - KitKat+ does not require the permissions - corrected the FileProvider resource xml * Added support for email attachments - support for a string path and native file types * Added attachments to the sample app * Updated the docs with the new types * Some fixes for iOS * Fix the mdoc target * regen docs * remove the obsolete armeabi ABI * Reworked the file logic to try and use public folders first - if the file is already exposed, then just use it directly - if the file is private, copy to an exposed location first - exposing the internal and external caches and the public/external files * Be more specific with the external storage permission name * Added some more depth to the comments here * Unnecessary else * Added base file info class * EmailAttachment now derives from FileBase * Added File Sharing * Keep track of IStorageFile internally * Prefer internal IStorageFile in UWP * Use attachment name properly in UWP * Add ctor to create from existing FileBase This will let us use UWP to create a new instance of something derived from FileBase with another instance of something else derived from FileBase, all while keeping track of the same `IStorageFile` instance. So we can conceivably do something like: ```csharp var mediaFile = await MediaPicker.PickPhotoAsync(); var attachment = new EmailAttachment(mediaFile); ``` * Add ctors for FileBase * Add ctors for ShareFileRequest * We can't use N on pre-N platforms * Updated the docs * Update some docs. * Bump * Gate Email/Share files with feature flags * Add sample for ShareFileRequest * Added test for share method in netstandard
- Loading branch information
Showing
59 changed files
with
3,881 additions
and
1,958 deletions.
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.