Skip to content

Commit 87f59a8

Browse files
authored
Fix Android MediaPicker Photo capture logic (#19844)
* Use result stream for photo/video source in sample * Use old capture photo logic In #18620 we changed the photo capture logic along with the video. The photo logic should have stayed how it was, this splits things out a bit so that the two paths are a bit more obviously separate, and reverts back to the older photo capture logic.
1 parent 7ae0222 commit 87f59a8

File tree

3 files changed

+75
-59
lines changed

3 files changed

+75
-59
lines changed

Diff for: src/Essentials/samples/Samples/View/MediaPickerPage.xaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
<Button Text="Pick video" Command="{Binding PickVideoCommand}" />
1919
<Button Text="Capture video" Command="{Binding CaptureVideoCommand}" />
2020

21-
<Image Source="{Binding PhotoPath}" IsVisible="{Binding ShowPhoto}" HeightRequest="300"/>
22-
<!--<MediaElement VerticalOptions="FillAndExpand" Source="{Binding VideoPath}" IsVisible="{Binding ShowVideo}" />-->
21+
<Image Source="{Binding PhotoSource}" IsVisible="{Binding ShowPhoto}" HeightRequest="300"/>
22+
<!--<MediaElement VerticalOptions="FillAndExpand" Source="{Binding VideoSource}" IsVisible="{Binding ShowVideo}" />-->
2323
</StackLayout>
2424
</ScrollView>
2525
</Grid>

Diff for: src/Essentials/samples/Samples/ViewModel/MediaPickerViewModel.cs

+20-32
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ namespace Samples.ViewModel
1010
{
1111
public class MediaPickerViewModel : BaseViewModel
1212
{
13-
string photoPath;
14-
string videoPath;
13+
ImageSource photoSource;
14+
ImageSource videoSource;
1515

1616
bool showPhoto;
1717
bool showVideo;
@@ -45,16 +45,16 @@ public bool ShowVideo
4545
set => SetProperty(ref showVideo, value);
4646
}
4747

48-
public string PhotoPath
48+
public ImageSource PhotoSource
4949
{
50-
get => photoPath;
51-
set => SetProperty(ref photoPath, value);
50+
get => photoSource;
51+
set => SetProperty(ref photoSource, value);
5252
}
5353

54-
public string VideoPath
54+
public ImageSource VideoSource
5555
{
56-
get => videoPath;
57-
set => SetProperty(ref videoPath, value);
56+
get => videoSource;
57+
set => SetProperty(ref videoSource, value);
5858
}
5959

6060
async void DoPickPhoto()
@@ -65,7 +65,7 @@ async void DoPickPhoto()
6565

6666
await LoadPhotoAsync(photo);
6767

68-
Console.WriteLine($"PickPhotoAsync COMPLETED: {PhotoPath}");
68+
Console.WriteLine($"PickPhotoAsync COMPLETED: {PhotoSource}");
6969
}
7070
catch (Exception ex)
7171
{
@@ -81,7 +81,7 @@ async void DoCapturePhoto()
8181

8282
await LoadPhotoAsync(photo);
8383

84-
Console.WriteLine($"CapturePhotoAsync COMPLETED: {PhotoPath}");
84+
Console.WriteLine($"CapturePhotoAsync COMPLETED: {PhotoSource}");
8585
}
8686
catch (Exception ex)
8787
{
@@ -97,7 +97,7 @@ async void DoPickVideo()
9797

9898
await LoadVideoAsync(video);
9999

100-
Console.WriteLine($"PickVideoAsync COMPLETED: {PhotoPath}");
100+
Console.WriteLine($"PickVideoAsync COMPLETED: {PhotoSource}");
101101
}
102102
catch (Exception ex)
103103
{
@@ -113,7 +113,7 @@ async void DoCaptureVideo()
113113

114114
await LoadVideoAsync(photo);
115115

116-
Console.WriteLine($"CaptureVideoAsync COMPLETED: {VideoPath}");
116+
Console.WriteLine($"CaptureVideoAsync COMPLETED: {VideoSource}");
117117
}
118118
catch (Exception ex)
119119
{
@@ -126,19 +126,13 @@ async Task LoadPhotoAsync(FileResult photo)
126126
// canceled
127127
if (photo == null)
128128
{
129-
PhotoPath = null;
129+
PhotoSource = null;
130130
return;
131131
}
132132

133-
// save the file into local storage
134-
var newFile = Path.Combine(FileSystem.CacheDirectory, photo.FileName);
135-
using (var stream = await photo.OpenReadAsync())
136-
using (var newStream = File.OpenWrite(newFile))
137-
{
138-
await stream.CopyToAsync(newStream);
139-
}
133+
var stream = await photo.OpenReadAsync();
134+
PhotoSource = ImageSource.FromStream(() => stream);
140135

141-
PhotoPath = newFile;
142136
ShowVideo = false;
143137
ShowPhoto = true;
144138
}
@@ -148,27 +142,21 @@ async Task LoadVideoAsync(FileResult video)
148142
// canceled
149143
if (video == null)
150144
{
151-
VideoPath = null;
145+
VideoSource = null;
152146
return;
153147
}
154148

155-
// save the file into local storage
156-
var newFile = Path.Combine(FileSystem.CacheDirectory, video.FileName);
157-
using (var stream = await video.OpenReadAsync())
158-
using (var newStream = File.OpenWrite(newFile))
159-
{
160-
await stream.CopyToAsync(newStream);
161-
}
149+
var stream = await video.OpenReadAsync();
150+
VideoSource = ImageSource.FromStream(() => stream);
162151

163-
VideoPath = newFile;
164152
ShowVideo = true;
165153
ShowPhoto = false;
166154
}
167155

168156
public override void OnDisappearing()
169157
{
170-
PhotoPath = null;
171-
VideoPath = null;
158+
PhotoSource = null;
159+
VideoSource = null;
172160

173161
base.OnDisappearing();
174162
}

Diff for: src/Essentials/src/MediaPicker/MediaPicker.android.cs

+53-25
Original file line numberDiff line numberDiff line change
@@ -66,46 +66,74 @@ public async Task<FileResult> CaptureAsync(MediaPickerOptions options, bool phot
6666
if (!OperatingSystem.IsAndroidVersionAtLeast(33))
6767
await Permissions.EnsureGrantedAsync<Permissions.StorageWrite>();
6868

69-
var capturePhotoIntent = new Intent(photo ? MediaStore.ActionImageCapture : MediaStore.ActionVideoCapture);
69+
var captureIntent = new Intent(photo ? MediaStore.ActionImageCapture : MediaStore.ActionVideoCapture);
7070

71-
if (!PlatformUtils.IsIntentSupported(capturePhotoIntent))
72-
throw new FeatureNotSupportedException($"Either there was no camera on the device or '{capturePhotoIntent.Action}' was not added to the <queries> element in the app's manifest file. See more: https://developer.android.com/about/versions/11/privacy/package-visibility");
71+
if (!PlatformUtils.IsIntentSupported(captureIntent))
72+
throw new FeatureNotSupportedException($"Either there was no camera on the device or '{captureIntent.Action}' was not added to the <queries> element in the app's manifest file. See more: https://developer.android.com/about/versions/11/privacy/package-visibility");
7373

74-
capturePhotoIntent.AddFlags(ActivityFlags.GrantReadUriPermission);
75-
capturePhotoIntent.AddFlags(ActivityFlags.GrantWriteUriPermission);
74+
captureIntent.AddFlags(ActivityFlags.GrantReadUriPermission);
75+
captureIntent.AddFlags(ActivityFlags.GrantWriteUriPermission);
7676

7777
try
7878
{
7979
var activity = ActivityStateManager.Default.GetCurrentActivity(true);
8080

81-
// Create the temporary file
82-
var ext = photo
83-
? FileExtensions.Jpg
84-
: FileExtensions.Mp4;
85-
var fileName = Guid.NewGuid().ToString("N") + ext;
86-
var tmpFile = FileSystemUtils.GetTemporaryFile(Application.Context.CacheDir, fileName);
81+
string captureResult = null;
8782

88-
string path = null;
83+
if (photo)
84+
captureResult = await CapturePhotoAsync(captureIntent);
85+
else
86+
captureResult = await CaptureVideoAsync(captureIntent);
87+
88+
// Return the file that we just captured
89+
return new FileResult(captureResult);
90+
}
91+
catch (OperationCanceledException)
92+
{
93+
return null;
94+
}
95+
}
8996

90-
void OnResult(Intent intent)
91-
{
92-
// The uri returned is only temporary and only lives as long as the Activity that requested it,
93-
// so this means that it will always be cleaned up by the time we need it because we are using
94-
// an intermediate activity.
97+
async Task<string> CapturePhotoAsync(Intent captureIntent)
98+
{
99+
// Create the temporary file
100+
var fileName = Guid.NewGuid().ToString("N") + FileExtensions.Jpg;
101+
var tmpFile = FileSystemUtils.GetTemporaryFile(Application.Context.CacheDir, fileName);
95102

96-
path = FileSystemUtils.EnsurePhysicalPath(intent.Data);
97-
}
103+
// Set up the content:// uri
104+
AndroidUri outputUri = null;
98105

99-
// Start the capture process
100-
await IntermediateActivity.StartAsync(capturePhotoIntent, PlatformUtils.requestCodeMediaCapture, onResult: OnResult);
106+
void OnCreate(Intent intent)
107+
{
108+
// Android requires that using a file provider to get a content:// uri for a file to be called
109+
// from within the context of the actual activity which may share that uri with another intent
110+
// it launches.
111+
outputUri ??= FileProvider.GetUriForFile(tmpFile);
101112

102-
// Return the file that we just captured
103-
return new FileResult(path);
113+
intent.PutExtra(MediaStore.ExtraOutput, outputUri);
104114
}
105-
catch (OperationCanceledException)
115+
116+
await IntermediateActivity.StartAsync(captureIntent, PlatformUtils.requestCodeMediaCapture, OnCreate);
117+
118+
return tmpFile.AbsolutePath;
119+
}
120+
121+
async Task<string> CaptureVideoAsync(Intent captureIntent)
122+
{
123+
string path = null;
124+
125+
void OnResult(Intent intent)
106126
{
107-
return null;
127+
// The uri returned is only temporary and only lives as long as the Activity that requested it,
128+
// so this means that it will always be cleaned up by the time we need it because we are using
129+
// an intermediate activity.
130+
path = FileSystemUtils.EnsurePhysicalPath(intent.Data);
108131
}
132+
133+
// Start the capture process
134+
await IntermediateActivity.StartAsync(captureIntent, PlatformUtils.requestCodeMediaCapture, onResult: OnResult);
135+
136+
return path;
109137
}
110138
}
111139
}

0 commit comments

Comments
 (0)