Saving a file in shared storage Xamarin forms - xamarin.forms

I need to save a file in the shared storage of android. I came across this link => https://developer.android.com/training/data-storage/shared/documents-files
I am using the dependency service and I am able to successfully save a file to desired location. But I am able to create only a blank file. I need to write some content into that file. Actually I created a thread few hours ago with android tag and got the solution where I had to override the OnActivityResult method and get the intent data. Now I have done it and I am able to get the intent data. But Now I dont know which path should i choose from the intent and how to open the file using the chosen path and how to write content into that chosen file. Any android + xamarin expert should be able to help me..
The android platform code to implement the write service:
Activity _activity;
private static int CREATE_FILE = 6835;
public WriteFileService()
{
_activity = CrossCurrentActivity.Current.Activity;
}
void IWriteService.WriteFile(string Content)
{
Intent intent = new Intent(Intent.ActionCreateDocument);
intent.AddCategory(Intent.CategoryOpenable);
intent.SetType("application/txt");
intent.PutExtra(Intent.ExtraTitle, "Invoice.txt");
_activity.StartActivityForResult(intent, CREATE_FILE);
}
The overriden OnActivityResultMethod:
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
Toast.MakeText(Application.Context, "requestCode is " + requestCode, ToastLength.Short).Show();
if (requestCode == 6835)
{
if (data != null)
{
Toast.MakeText(Application.Context,
data.GetType().ToString(),
ToastLength.Short).Show();
}
}
base.OnActivityResult(requestCode, resultCode, data);
}
This is the screen of the Intent Data from OnActivityResult

Use this in your Android project to save a stream to file:
public async Task SaveAndView(string fileName, String contentType, MemoryStream stream)
{
try
{
string root = null;
//Get the root path in android device.
if (Android.OS.Environment.IsExternalStorageEmulated)
{
root = Android.OS.Environment.ExternalStorageDirectory.ToString();
}
else
root = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
//Create directory and file
Java.IO.File myDir = new Java.IO.File(root + "/meusarquivos");
myDir.Mkdir();
Java.IO.File file = new Java.IO.File(myDir, fileName);
//Remove if the file exists
if (file.Exists()) file.Delete();
//Write the stream into the file
FileOutputStream outs = new FileOutputStream(file);
outs.Write(stream.ToArray());
outs.Flush();
outs.Close();
}
catch (Exception ex)
{
PostLog.AppCenterLogExcecao(ex, new Dictionary<string, string> { { "origem", "OrderViewModel - 159" } });
}
}
And in your shared code:
await DependencyService.Get<ISave>().SaveAndView(OrderId.ToString() + ".pdf", "application/pdf", stream);
Be sure to ask for the permissions before using the code.

To save the file. ive used this code
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
if (requestCode == 6835)
{
if (data != null)
{
Android.Net.Uri uri = Android.Net.Uri.Parse(data.DataString);
var stream = ContentResolver.OpenOutputStream(uri, "w");
byte[] byteArray = Encoding.UTF8.GetBytes("Hi Thameem. I am your file.");
MemoryStream stream1 = new MemoryStream(byteArray);
stream1.CopyTo(stream);
stream.Flush();
stream.Close();
Toast.MakeText(Application.Context,
"The file has been exported successfully",
ToastLength.Short).Show();
}
}
base.OnActivityResult(requestCode, resultCode, data);
}

Related

Playing video stream from mp4 file with moov atom at end using libvlcsharp

I want to play video replay from low-end surveillance camera. Replays are saved on the camera in .mp4 format, with moov atom at the end. It's possible to retrieve file via http request using digset authentication. Approximate size of each video file is 20 MB, but download speed is only 3 Mbps, so downloading whole file takes about 60 s. This is to long, so I want to start displaying video before whole file will be downloaded.
Web browsers handles this kind of problem by reading end of file at the begining. I want to achieve same goal using c# and libvlcsharp, so created HttpMediaInput class.
public class HttpMediaInput : MediaInput
{
private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private HttpClientHandler _handler;
private HttpClient _httpClient;
private string _url;
Stream _stream = null;
public HttpMediaInput(string url, string username, string password)
{
_url = url;
_handler = new HttpClientHandler() { Credentials = new NetworkCredential(username, password) };
_httpClient = new HttpClient(_handler);
}
public override bool Open(out ulong size)
{
size = ulong.MaxValue;
try
{
_stream = _httpClient.GetStreamAsync(_url).Result;
base.CanSeek = _stream.CanSeek;
return true;
}
catch (Exception ex)
{
logger.Error(ex, $"Exception occurred during sending stream request to url: {_url}");
return false;
}
}
public unsafe override int Read(IntPtr buf, uint len)
{
try
{
byte[] buffer = new byte[len];
int bytesReaded = _stream.Read(buffer, 0, buffer.Length);
logger.Trace($"Bytes readed: {bytesReaded}");
Span<byte> byteSpan = new Span<byte>(buf.ToPointer(), buffer.Length);
buffer.CopyTo(byteSpan);
return bytesReaded;
}
catch (Exception ex)
{
logger.Error(ex, "Stream read exception");
return -1;
}
}
...
}
It works great for mp4 files that have all necessary metadata stored on the beginning, but no video is displayed in case of my camera.
Assuming that I will be able to download moov atom from mp4 using http range requests, how to provide this data to libvlc? Is it even possible?
I'm developing application using C#, WPF, dotnet framework.
VLC cannot play files from camera because http digest auth with md5 is considered to be deprecated (related issue in VLC repo).
However, I was able to resolve this problem following cube45 suggestions, I implemented range requests.
public override bool Open(out ulong size)
{
size = ulong.MaxValue;
try
{
HttpRequestMessage requestMessage = new HttpRequestMessage { RequestUri = new Uri(_url) };
requestMessage.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue();
requestMessage.Method = HttpMethod.Head;
var response = _httpClient.SendAsync(requestMessage).Result;
size = (ulong)response.Content.Headers.ContentLength;
_fileSize = size;
logger.Trace($"Received content lenght | {size}");
base.CanSeek = true;
return true;
}
catch (Exception ex)
{
logger.Error(ex, $"Exception occurred during sending head request to url: {_url}");
return false;
}
}
public unsafe override int Read(IntPtr buf, uint len)
{
try
{
HttpRequestMessage requestMessage = new HttpRequestMessage { RequestUri = new Uri(_url) };
long startReadPosition = (long)_currentPosition;
long stopReadPosition = (long)_currentPosition + ((long)_numberOfBytesToReadInOneRequest - 1);
if ((ulong)stopReadPosition > _fileSize)
{
stopReadPosition = (long)_fileSize;
}
requestMessage.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue(startReadPosition, stopReadPosition);
requestMessage.Method = HttpMethod.Get;
HttpResponseMessage response = _httpClient.SendAsync(requestMessage).Result;
byte[] readedBytes = response.Content.ReadAsByteArrayAsync().Result;
int readedBytesCount = readedBytes.Length;
_currentPosition += (ulong)readedBytesCount;
logger.Trace($"Bytes readed | {readedBytesCount} | startReadPosition {startReadPosition} | stopReadPosition | {stopReadPosition}");
Span<byte> byteSpan = new Span<byte>(buf.ToPointer(), (int)len);
readedBytes.CopyTo(byteSpan);
return readedBytesCount;
}
catch (Exception ex)
{
logger.Error(ex, "Media reading general exception");
return -1;
}
}
public override bool Seek(ulong offset)
{
try
{
logger.Trace($"Seeking media with offset | {offset}");
_currentPosition = offset;
return true;
}
catch (Exception ex)
{
logger.Error(ex, "MediaInput seekeing general error");
return false;
}
}
This solution seams to work, but there are two unresolved problems:
There is about 8s lag between libvlcsharp starts reading stream and video goes live (waiting time in web browser is about 2s).
Some part of video file at the end is not displayed, because the buffer is too short to hold whole file inside. Related thread

Xamarin.Forms Directory Picker

I would like to save a file in a user selected folder, thats why I would like to provide a directory list to user and user will be able to choose the directory where he wants to export the data. Unfortuntely I could not find any example for directory/folder picker, I just found a file picker which is not useful for me..
https://github.com/jfversluis/FilePicker-Plugin-for-Xamarin-and-Windows
Is there any component for picking a folder for Xamarin.Forms? Actually I am just doing for Android but we use Xamarin.forms
There is none I can think of.
With netstandard everything is way more simple as you can use the classic c# File api to get the folders.
You just have to know the mappings between special folders and android folders (per example):
System.Environment.SpecialFolder Path
ApplicationData INTERNAL_STORAGE/.config
Desktop INTERNAL_STORAGE/Desktop
LocalApplicationData INTERNAL_STORAGE/.local/share
MyDocuments INTERNAL_STORAGE
MyMusic INTERNAL_STORAGE/Music
MyPictures INTERNAL_STORAGE/Pictures
MyVideos INTERNAL_STORAGE/Videos
Personal INTERNAL_STORAGE
source: https://learn.microsoft.com/en-US/xamarin/android/platform/files/
same for ios:
https://learn.microsoft.com/en-US/xamarin/ios/app-fundamentals/file-system
But it's really easy to implement, just enumerate all folders and display them in a ListView.
EDIT: more details on implementation.
In fact you want to code a "directory explorer", it's easy, here is the concept.
You have a ListView in your Page
You have a Cancel button and a Select button in your Page
You have a CurrentPath in your ViewModel
You bind CurrentPath to the Title of your Page
You have an List<DirectoryViewModel> Directories in your ViewModel
Each time a user click on a item from the list:
You add the directory name in your current path
You get all the directories from the new path, and update your Directories property (don't forget RaisePropertyChange(nameof(Directories)))
The ListView will be updated accordingly
Each time you back:
You remove last part of your current path
same as before
If you arrive to root path "/", you do nothing when clicking on back.
Oh and you could use this Grid component to instead of the ListView, will be nicer ;)
https://github.com/roubachof/Sharpnado.Presentation.Forms#grid-Layout
You can edit this to make it work..
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Java.IO;
using Java.Util;
namespace Android.Basic.IO
{
public class DirectoryPicker : ListActivity
{
public const String START_DIR = "startDir";
public const String ONLY_DIRS = "onlyDirs";
public const String SHOW_HIDDEN = "showHidden";
public const String CHOSEN_DIRECTORY = "chosenDir";
public const int PICK_DIRECTORY = 43522;
private File dir;
private Boolean showHidden = false;
private bool onlyDirs = true;
public override void OnCreate(Bundle savedInstanceState, PersistableBundle persistentState)
{
base.OnCreate(savedInstanceState, persistentState);
Bundle extras = Intent.Extras;
dir = OS.Environment.ExternalStorageDirectory;
if (extras != null)
{
String preferredStartDir = extras.GetString(START_DIR);
showHidden = extras.GetBoolean(SHOW_HIDDEN, false);
onlyDirs = extras.GetBoolean(ONLY_DIRS, true);
if (preferredStartDir != null)
{
File startDir = new File(preferredStartDir);
if (startDir.IsDirectory)
{
dir = startDir;
}
}
}
SetContentView(Resource.Layout.folder_chooser_activity);
var title = dir.AbsolutePath.ToString();
Title = (title);
Button btnChoose = (Button)FindViewById(Resource.Id.btnChoose);
String name = dir.Name;
if (name.Length == 0)
name = "/";
btnChoose.Text = ("Choose " + "'" + name + "'");
btnChoose.Click += delegate
{
returnDir(dir.AbsolutePath);
};
ListView lv = this.ListView;
lv.TextFilterEnabled = (true);
if (!dir.CanRead())
{
Context context = ApplicationContext;
String msg = "Could not read folder contents.";
Toast.MakeText(context, msg, ToastLength.Long).Show();
return;
}
var files = filter(dir.ListFiles(), onlyDirs, showHidden);
String[] names = Names(files);
ListAdapter = (new ArrayAdapter<String>(this, Resource.Layout.folder_chooser_item, names));
lv.ItemClick += (ff, gg) =>
{
var position = gg.Position;
if (!files[gg.Position].IsDirectory)
return;
String path = files[position].AbsolutePath;
var intent = new Intent(this, typeof(DirectoryPicker));
intent.PutExtra(DirectoryPicker.START_DIR, path);
intent.PutExtra(DirectoryPicker.SHOW_HIDDEN, showHidden);
intent.PutExtra(DirectoryPicker.ONLY_DIRS, onlyDirs);
StartActivityForResult(intent, PICK_DIRECTORY);
};
}
protected void OnActivityResult(int requestCode, int resultCode, Intent data)
{
if (requestCode == PICK_DIRECTORY && resultCode == (int)Result.Ok)
{
Bundle extras = data.Extras;
String path = (String)extras.Get(DirectoryPicker.CHOSEN_DIRECTORY);
returnDir(path);
}
}
private void returnDir(String path)
{
Intent result = new Intent();
result.PutExtra(CHOSEN_DIRECTORY, path);
SetResult(Result.Ok, result);
Finish();
}
public List<File> filter(File[] file_list, bool onlyDirs, bool showHidden)
{
var files = new List<File>();
foreach (var file in file_list)
{
if (onlyDirs && !file.IsDirectory)
continue;
if (!showHidden && file.IsHidden)
continue;
files.Add(file);
}
Collections.Sort(files);
return files;
}
public String[] Names(List<File> files)
{
String[] names = new String[files.Count];
int i = 0;
foreach (var file in files)
{
names[i] = file.Name;
i++;
}
return names;
}
}
}
Start activity as result then catch in OnActivityResult
if (requestCode == DirectoryPicker.PICK_DIRECTORY && resultCode == Result.Ok)
{
Bundle extras = data.Extras;
String path = (String)extras.Get(DirectoryPicker.CHOSEN_DIRECTORY);
// do stuff with path
}

Switch from Visual Studio 2012 --> 2019 Broke async Task and FileResult

An ASP.NET MVC solution that was working fine in VS 2012 stopped working in 2019 and I cannot find what has changed to break it.
Originally I had the code in the first block - the async task would go to the file storage and retrieve the file, and then the file was sent to the browser using a FileResult that the controller called automatically. After a VERY painful change to VS 2019 and updating everything (.NET runtime, 3rd party libraries, etc.) I have the application working again except for this issue.
I tried creating a new FileStreamResult (which is in the 2nd block) but that does not get called either. When I click on a link that calls this:
<a href="/Cert/File?folder=&filename=#HttpUtility.UrlEncode(FilePath)" ...
It gives me a blank page instead of downloading the file as it used to.
public async Task FileAsync(string folder, string filename)
{
AsyncManager.OutstandingOperations.Increment();
var ReadObjectTask = _fileStorageProvider.ReadObjectDataAsync(folder, filename);
Stream ROResult = await ReadObjectTask;
AsyncManager.Parameters["stream"] = ROResult;
AsyncManager.Parameters["filename"] = filename;
AsyncManager.OutstandingOperations.Decrement();
}
public FileResult FileCompleted(Stream stream, string filename)
{
if (stream == null)
{
return File(Server.MapPath(Url.Content("~/Content/bad_file.png")), "image/png");
}
var file = new FileStreamResult(stream, MIMEAssistant.GetMIMEType(filename));
if (filename.Contains("/"))
{
filename = filename.Split('/').Last();
}
file.FileDownloadName = filename;
return file;
}
Here is the FileStreamResult I tried:
public System.Web.Mvc.FileStreamResult FileCompleted(Stream stream, string contentType, string filename)
{
if (stream == null)
{
string bFile = Server.MapPath(Url.Content("~/Content/bad_file.png"));
Stream blankfile = System.IO.File.OpenRead(bFile);
return File(blankfile, MIMEAssistant.GetMIMEType(bFile), System.IO.Path.GetFileName(bFile));
}
if (filename.Contains("/"))
{
filename = filename.Split('/').Last();
}
return File(stream, MIMEAssistant.GetMIMEType(filename), filename);
}
(The filename.Contains part is old code from a predecessor that I just need to replace with Path.GetFileName - sorry I did not clean it up before I posted.)
I decided to make the Async Task one of type and moved the
stream processing into that procedure to solve my problem. I do not know
why the Async Task that was working in 2012 stopped in 2019.
public async Task<FileResult> FileAsync(string folder, string filename)
{
AsyncManager.OutstandingOperations.Increment();
var ReadObjectTask = _fileStorageProvider.ReadObjectDataAsync(folder, filename);
Stream ROResult = await ReadObjectTask;
AsyncManager.Parameters["stream"] = ROResult;
AsyncManager.Parameters["filename"] = filename;
AsyncManager.OutstandingOperations.Decrement();
if (ROResult == null)
{
return File(Server.MapPath(Url.Content("~/Content/bad_file.png")), "image/png");
}
var file = new FileStreamResult(ROResult, MIMEAssistant.GetMIMEType(filename));
file.FileDownloadName = System.IO.Path.GetFileName(filename);
return file;
}

How to scan dtable drool file if there is any changes in file and load it again using kiescanner drool version 7.4..final

I am working on drool dtable xls file with spring.
i have implemented the business rules in xls file using external location and then with the help of kie services i am executing rules.
Following is the code snippet that's how i am loading rules in engine.
at the start of spring initialization i am calling init() method
see below spring configuration.
<bean id="droolsService" class="com.example.drools.DroolsServiceImpl" init-method="init">
Java Code
public void init() {
LOG.info("inside init");
KieSession kieSession;
for (RequestType type : droolsMap.keySet()) {
try {
kieSession = getKieSession(this.getDroolsMap().get(type));
droolsRules.put(type, kieSession);
} catch (Exception e) {
LOG.error("Failed to load kiesession:", e);
throw new RuntimeException(e);
}
}
}
private KieSession getKieSession(final String file) throws DroolsParserException, IOException, BiffException {
KieServices kieServices = KieServices.Factory.get();
KieFileSystem kfs = kieServices.newKieFileSystem();
InputStream stream = null;
String drl = null;
String RULE_PATH = "src/main/resources/";
SpreadsheetCompiler converter = new SpreadsheetCompiler();
//Workbook workbook = Workbook.getWorkbook(DroolsServiceImpl.class.getResourceAsStream(file));
Workbook workbook = Workbook.getWorkbook(new FileInputStream(file));
LOG.info("Loading rule file " + file);
for (Sheet sheet : workbook.getSheets()) {
LOG.info("Loading Sheet " + sheet.getName());
stream = new FileInputStream(file);
drl = converter.compile(stream, sheet.getName());
//StringReader reader = new StringReader(drl);
String DRL_FILE = RULE_PATH + sheet.getName() + ".drl";
System.out.println("Drool file added ::: " + DRL_FILE);
kfs.write(DRL_FILE, ResourceFactory.newReaderResource(new StringReader(drl)));
stream.close();
}
KieBuilder kieBuilder = kieServices.newKieBuilder(kfs).buildAll();
KieContainer kieContainer = kieServices.newKieContainer(kieServices.getRepository().getDefaultReleaseId());
KieSessionConfiguration conf = SessionConfiguration.newInstance();
KieSession ksession = kieContainer.newKieSession(conf);
if (kieBuilder.getResults().hasMessages(Message.Level.ERROR)) {
List<Message> errors = kieBuilder.getResults().getMessages(Message.Level.ERROR);
StringBuilder sb = new StringBuilder("Errors:");
for (Message msg : errors) {
sb.append("\n " + msg);
}
try {
throw new Exception(sb.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (stream != null)
stream.close();
if (workbook != null)
workbook.close();
}
}
return ksession;
}
Everything working perfect but the problem is i am not able to scan the file changes. If files is modified then i have to restart the server in order to sync the changes.
I have tried listener to load specific init() method after xls dtable has any changes but its not working , same old result is coming.
I have tried kiescanner but i am not able to get the concept.
KieScanner is loading maven kjar so how do i suppose to create kjar.
I just wanted to kie api scan if any changes in the drool file and try to reload whole changes in kiecontainer without server restarting.
Found the answer myself, Posting because it will help someone who needed.
What I did , I have used apache VFS File Monitor-
DefaultFileMonitor fm = new DefaultFileMonitor(new CustomFileListener());
When file will modified , create or get deleted it will call CustomFileListener.
Following is the implementation of CustomFileListener.
import org.apache.commons.vfs2.FileChangeEvent;
import org.apache.commons.vfs2.FileListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.support.XmlWebApplicationContext;
public class CustomFileListener implements FileListener {
private static final Logger LOG = LoggerFactory.getLogger(CustomFileListener.class);
#Override
public void fileCreated(FileChangeEvent fileChangeEvent) throws Exception {
}
#Override
public void fileDeleted(FileChangeEvent fileChangeEvent) throws Exception {
}
#Override
public void fileChanged(FileChangeEvent fileChangeEvent) throws Exception {
LOG.debug(" Under FileChanged Method");
LOG.debug(" File has been changed hence reinitializing init method = " + fileChangeEvent.getFile().getName().getPath());
XmlWebApplicationContext xmlWebApplicationContext =
(XmlWebApplicationContext) ContextLoader.getCurrentWebApplicationContext();
DefaultListableBeanFactory defaultListableBeanFactory =
(DefaultListableBeanFactory) xmlWebApplicationContext.getBeanFactory();
DroolsServiceImpl droolsService = (DroolsServiceImpl) defaultListableBeanFactory.getBean("droolsService");
droolsService.init();
}
}
What i did when the file will change, It will call fileChanged method.
In that i have fetched cached bean(DroolServiceImpl) from ContextLoader.getCurrentWebApplicationContext(); and called its init() method.
So this it will reload whole process and reinitialize the KieModule,KieRepository.

No 'output' extra specified Exception

I use the Google Photo app to pick gallery photo and then when I crop the picture and save, it catches an exception, Here is my code :
goto_picture.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
intent = new Intent(Intent.ACTION_PICK, null);
intent.setDataAndType(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
IMAGE_UNSPECIFIED);
startActivityForResult(intent, PHOTO_ZOOM);
dialog.cancel();
}
});
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == Activity.RESULT_OK) {
if (requestCode == PHOTO_GRAPH) {
startPhotoZoom(Uri.fromFile(file));
String imagePath = SystemUtils.getSDPath() + "/temp.jpg";
File picture = new File(imagePath);
if (picture.exists()) {
pictureBitmap = BitmapFactory.decodeFile(imagePath);
ImageUtils.SaveCacheBitmap(pictureBitmap);
rvEditAvatar.setImageBitmap(pictureBitmap);
}
}
if (requestCode == PHOTO_ZOOM) {
startPhotoZoom(data.getData());
}
if (requestCode == PHOTO_RESULT) {
Bundle extras = data.getExtras();
if (extras != null) {
pictureBitmap = extras.getParcelable("data");
ByteArrayOutputStream stream = new ByteArrayOutputStream();
pictureBitmap.compress(Bitmap.CompressFormat.JPEG, 100,
stream);
ImageUtils.SaveCacheBitmap(pictureBitmap);
rvEditAvatar.setImageBitmap(pictureBitmap);
}
}
}
}
public void startPhotoZoom(Uri uri) {
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(uri, "image/*");
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", 180);
intent.putExtra("outputY", 180);
intent.putExtra("return-data", true);
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
startActivityForResult(intent, PHOTO_RESULT);
}
Logcat:
Process: com.google.android.apps.photos, PID: 7031
java.lang.RuntimeException: Unable to resume activity
{com.google.android.apps.photos/com.google.android.apps.photos.photoeditor.intents.EditActivity}:
java.lang.UnsupportedOperationException: No 'output' extra specified
and can not save to specified inputUri:
content://com.google.android.apps.photos.contentprovider/0/1/content%3A%2F%2Fmedia%2Fexternal%2Fimages%2Fmedia%2F72072/ACTUAL
As the exception said, you have to specify output extra like the following code.
intent.putExtra(MediaStore.EXTRA_OUTPUT, someOutPutPath);
And return data is not secure in case of big image cropped which may cause crash. I think that's why it forces you to use an output extra but not the data directly. So you may set the return-data to false as well:
intent.putExtra("return-data", false);
I meet this problem today, and solved by double check the data pass-back.I test following code on both Android L and Android 4. On Android L the fileUri is not empty while pre Android L we got fileUri null(in this case, I got the bitmap by simply getData).
private Bitmap decodeBitmapFromCrop(Intent data) {
Bundle extras = data.getExtras();
Bitmap photo = null;
if (extras != null) {
photo = extras.getParcelable("data");
} else {
Uri fileUri = data.getData();
if (fileUri != null) {
try {
photo = MediaStore.Images.Media.getBitmap(getContentResolver(), fileUri);
} catch (IOException e) {
XXLog.d(TAG, "Media.getBitmap", e);
}
}
}
return photo;
}

Resources