android trouble with saving pictures to internal memory - android

I'm writing an app for school, of which one of the biggest parts is taking pictures. I came to conclusion that it would be perfect if I could just save them on Internal Storage, so they would be automatically removed during uninstall process.
I ran across few code samples, proving me there is no easy way to do that (and I realized saving images on external memory is a piece of cake too). However, I managed to find that question: Trouble writing internal memory android and came up with something similiar. After some time testing I ran across "Unable to resume activity" exception, but fortunately I've found a tweak: image from camera intent issue in android
Now my code looks like this:
private void dispatchTakePictureIntent(int actionCode) {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), FITPAL_TEMP_PICTURE_NAME);
Uri outputFileUri = Uri.fromFile(file);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);
startActivityForResult(takePictureIntent, actionCode);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case ACTION_TAKE_PHOTO_B: {
if (resultCode == RESULT_OK) {
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), FITPAL_TEMP_PICTURE_NAME);
Bitmap picture = BitmapFactory.decodeFile(file.getAbsolutePath());
if (picture == null) {
showShortToast(R.string.problem_with_taking_a_picture);
return;
}
File dir = getDir(Environment.DIRECTORY_PICTURES, Context.MODE_PRIVATE);
File internalFile = null;
internalFile = new File(dir, generateUniqueFilename());
internalFile.setReadable(true);
internalFile.setWritable(true);
internalFile.setExecutable(true);
try {
internalFile.createNewFile();
FileOutputStream fos = new FileOutputStream(internalFile);
BufferedOutputStream bos = new BufferedOutputStream(fos);
picture.compress(Bitmap.CompressFormat.JPEG, JPEG_FILE_QUALITY, bos);
bos.flush();
bos.close();
String filePath = internalFile.getAbsolutePath();
pictureFilePaths.add(filePath);
addPictureThumbnail(filePath);
Log.d("mounted?", "" + Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED));
Log.d("delete test 23123123", "" + file.getAbsolutePath() + " " + file.delete());
} catch (Exception e) {
Log.e("eks", e.getMessage());
showShortToast(R.string.problem_with_taking_a_picture);
}
}
else if (resultCode == RESULT_CANCELED) {
showShortToast(R.string.taking_picture_has_been_cancelled);
}
break;
}
default:
break;
}
}
Basically what I'm trying to do is write a picture to External Storage and move it to Internal Storage. It works in 80% of cases, it's not 100% because it sometimes just "resets" the activity - the new empty layout shows up, just as if it was just created. I have no idea what is happening. I realized that both onSaveInstanceState and onRestoreInstanceState fire in that case and I've no idea why (no exceptions are thrown along the way).
The activity that is invoking all that code is extending FragmentActivity. I've read there are some bugs that might cause that in older versions of Android Support Package, but I've just updated it to version 13 and the problem still persists.
The other thing is that the file saved on external storage won't delete - file.delete() always returns false. Is it a matter of me not having an sdcard? (I mean I have a slot in my phone, but I just have Internal Storage of 16GB). The file gets saved to /mnt/sdcard/Pictures/ folder as if it was emulated somehow.
In advance to the questions: I have both android.permission.READ_EXTERNAL_STORAGE and android.permission.WRITE_EXTERNAL_STORAGE permissions set:
I tried replacing Environment.getExternalStoragePublicDirectory with getCacheDir, getExternalCacheDir and none of them worked either..
I'm testing all that stuff on my Samsung Galaxy SII with Android 4.0.4 (stock)

Related

Android - VideoView unable to play video in external storage

Inside of a fragment in my android application, I take a video using an intent and then save it to my external storage:
private void dispatchTakeVideoIntent() {
Intent takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
if (takeVideoIntent.resolveActivity(getActivity().getPackageManager()) != null) {
startActivityForResult(takeVideoIntent, REQUEST_VIDEO_CAPTURE);
}
}
#Override
public void onActivityResult(int requestCode, int resultCode, #Nullable Intent intent) {
if (requestCode == REQUEST_VIDEO_CAPTURE && resultCode == Activity.RESULT_OK) {
takenVideoUri = intent.getData();
String ext = ".mp4";
String filename = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss.SSS")
.format(System.currentTimeMillis()) + ext;
File root = getContext().getExternalFilesDir(Environment.DIRECTORY_MOVIES);
File file = new File(root, filename);
try(
InputStream is = getContext().getContentResolver().openInputStream(takenVideoUri);
FileOutputStream fos = new FileOutputStream(file)
) {
BufferedOutputStream bos = new BufferedOutputStream(fos);
byte[] buf = new byte[1024];
while(is.read(buf)!=-1) {
bos.write(buf);
}
bos.close();
fos.close();
videoUri = Uri.fromFile(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Then, inside of another fragment, I have a VideoView to which I want to pass the Uri of the saved video for it to play it.
Uri videoUri = /* gotten from the other fragment */
videoView.setVideoURI(videoUri);
However, when I open that fragment, I get the errors:
2022-04-28 18:53:59.273 23764-23821/my.app E/MediaPlayerNative: error (1, -2147483648)
2022-04-28 18:53:59.305 23764-23764/my.app E/MediaPlayer: Error (1,-2147483648)
I understand those are generic errors, which makes it worse because I have no clue as to where the error is.
My manifest file does include permission for both reading and writing external storage. I have tried saving the video to internal storage too, but nothing changed.
What am I doing wrong?
It seems to me the file is incomplete/empty. You could check its size using file.length() before trying to play it. Copying the file might be unnecessary as you could simply call takeVideoIntent.putExtra(EXTRA_OUTPUT, uri) to the intent to specify the path(uri) the captured video's location.
What usually happens with camera capture outputs is that onActivityResult is called before the file contents were properly written to storage (bytes flushed and file closed) especially with slower storages (sd cards) so what you get is an incomplete file (often times even empty).
You can delay video playing after onActivityResult while checking if the video file's size and last modified time (file.lastModified()) have increased to see if writing/flushing has actually finished and you can play it. If it's a long video (more than a few seconds) and depending on the enconding used you may start playing it before its fully written to storage but if the video player reaches the end before the recording app has finished writing the playing will end abruptly and you'll probably need to reload the video in the player.
It's a big problem of linux with the slow writing on (ex/V)FAT partitions which are used on portable storage for interoperability with other OSs (Windows) especially over USB. It's always a long wait to copy a few GB of data on a USB stick.

Storage Access Framework ACTION_CREATE_DOCUMENT on Google Drive results in 0 byte file sometimes

Suddendly my app's Google Drive backup/restore via Storage Access Framework feature stopped working properly. Users report it failing sometimes, and sometimes not, without a clear reason. Internal or external storage options instead work as expected.
This is my code:
private void createDocument(String fileName) {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("application/json");
intent.putExtra(Intent.EXTRA_TITLE, fileName);
try {
startActivityForResult(intent, REQUEST_CREATE_DOCUMENT);
}
catch (ActivityNotFoundException ex) {
Toast.makeText(this, R.string.message_saf_not_supported, Toast.LENGTH_LONG).show();
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK && data != null) {
Uri uri = data.getData();
if (requestCode == REQUEST_CREATE_DOCUMENT && uri != null) {
ParcelFileDescriptor fileDescriptor = getApplicationContext().getContentResolver().openFileDescriptor(Uri.parse(uriString), "w");
if (fileDescriptor != null) {
FileOutputStream fileOutputStream = new FileOutputStream(fileDescriptor.getFileDescriptor());
// Write bytes...
fileOutputStream.close();
fileDescriptor.close();
}
}
}
super.onActivityResult(requestCode, resultCode, data);
}
I debugged my code and experienced this strange behavior.
If data to write is little and code is executed almost immediately it works most of the times.
If data is big (and usually is, around 10MB) or I put a breakpoint on my onActivityResult method openFileDescriptor or FileOutputStream creation throw a FileNotFoundException or even if that exception is not raised it fails silently and resulting file on Google Drive is 0 bytes long.
I even tried putting the ParcelFileDescriptor/FileOutputStream part in a background thread, AsyncTask, Worker without any luck. Problem is the same.
This is upsetting my users and making my ratings go way down. I'm sure I didn't anything on my code in the last two months, so the problem must be elsewhere, but I don't know...
EDIT: I might add that is seems somehow related to the Google Drive app, because if I clear its data or cache the subsequent uploads work fine, until it breaks again.

How to change the directory of SquareCamera library?

I am using SquareCamera library (https://github.com/boxme/SquareCamera) for taking square picture.The problem I am facing is that SquareCamera is creating its own folder where taken pics are getting stored. I want these pics to store in my own folder. I don't know how to achieve that. I am very new to android. Below is the code where instead of default camera I am calling its own class.
public void onLaunchCamera(View view) {
// create Intent to take a picture and return control to the calling application
Intent intent = new Intent(this,CameraActivity.class);
// Start the image capture intent to take photo
startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE);
And this is the onActivityResult method
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE) {
if (resultCode == RESULT_OK) {
Uri takenPhotoUri = data.getData();
Bitmap takenImage = BitmapFactory.decodeFile(takenPhotoUri.getPath());
imageView.setImageBitmap(takenImage);
I thought about saving this bitmap into my own folder but I couldn't think how to delete the created directory of SquareCamera.
So I found the solution. I added the library as a module in my app. Referring (https://www.youtube.com/watch?v=1MyBO9z7ojk). And there I changed the source code a little bit and now it's working perfect.
I'm a bit long in the tooth at Android and am not 100% with the new Uri methods of file access enforced since KitKat. For conventional file access you can get a private writeable file using.
private static final File OUTPUT_DIR = Environment.getExternalStorageDirectory();
FileOutputStream fos;
void yourMethodBeginsHere() {
String outputPath = new File(OUTPUT_DIR, "test.png").toString();
try {
fos = new FileOutputStream(outputPath, false);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
//Work with file
}
If you need a truly external file path please refer to the excellent answer already existing at https://stackoverflow.com/a/26765884/5353361 which deals fully with the new Uri based system of permissions and the integrated file explorer.

Saving a picture into memory

I need to use the device camera to take a picture, save it into its memory and get the uri so I can email it afterwards.
I'm using Android 3.2 on a device with no memory card (just 11 gigs of internal storage).
private void takePicture(){
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, 1);
}
public void onActivityResult(int requestCode, int resultCode, Intent data){
if(requestCode == CAMERA_PIC_REQUEST && resultCode == Activity.RESULT_OK){
picture = (Bitmap) data.getExtras().get("data");
pictureView.setImageBitmap(picture);
ContentValues values = new ContentValues();
values.put(Images.Media.TITLE, "Picture");
values.put(Images.Media.BUCKET_ID, "picture_ID");
values.put(Images.Media.DESCRIPTION, "");
values.put(Images.Media.MIME_TYPE, "image/jpeg");
pictureUri = getContentResolver().insert(Media.EXTERNAL_CONTENT_URI, values);
OutputStream outstream;
try{
outstream = getContentResolver().openOutputStream(pictureUri);
picture.compress(Bitmap.CompressFormat.JPEG, 100, outstream);
outstream.close();
}catch(FileNotFoundException e){
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Any help from someone more experienced with this would be greatly appreciated.
To keep something in memory you'd just declare a variable or data structure to hold it; in this case some form of a byte array seems promising. I don't think that would work very well in this case, though. The heap in Android is usually capped at 16 or 25 mb, and depending on what else your app is doing storing photos would get to an "Out of Memory" exception quickly.
So with no SD card, this leaves you with writing to internal storage. I don't know which, if any, directories on internal storage the app can write without root access and/or mounting the system read/write, but I think the best bet is /data/tmp. See if you can save the pic there. If not, mounting the system read/write isn't so bad either.

Trouble writing internal memory android

void launchImageCapture(Activity context) {
Uri imageFileUri = context.getContentResolver()
.insert(Media.INTERNAL_CONTENT_URI, new ContentValues());
m_queue.add(imageFileUri);
Intent i = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
i.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, imageFileUri);
context.startActivityForResult(i, ImportActivity.CAMERA_REQUEST);
}
The above code, which has always worked, is now generating this exception for me at insert().
java.lang.UnsupportedOperationException: Writing to internal storage is not supported.
at com.android.providers.media.MediaProvider.generateFileName(MediaProvider.java:2336)
at com.android.providers.media.MediaProvider.ensureFile(MediaProvider.java:1851)
at com.android.providers.media.MediaProvider.insertInternal(MediaProvider.java:2006)
at com.android.providers.media.MediaProvider.insert(MediaProvider.java:1974)
at android.content.ContentProvider$Transport.insert(ContentProvider.java:150)
at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:140)
at android.os.Binder.execTransact(Binder.java:287)
at dalvik.system.NativeStart.run(Native Method)
It is not a space issue, and the only thing I changed was the package of an unrelated class all together. Also, I restarted my phone.
Facing same problem here, I was happy to find this thread. Even though two things were bugging me in this workaround, this post had me looking in the right direction. I'd like to share my own workaround/solution.
Let me begin by stating what I did not see myself living with.
First, I did not want to leave the application private file as MODE_WORLD_WRITEABLE. This looks like non-sense to me, although I cannot figure exactly how another application could access this file unless knowing where to look for it with complete name and path. I'm not saying it is necessarily bad for your scenario, but it is still bugging me somehow. I would prefer to cover all my bases by having picture files really private to my app. In my business case, pictures are of no use outside of the application and by no means should they be deleteable via, say, the Android Gallery. My app will trigger cleanup at an appropriate time so as to not vampirize Droid device storage space.
Second, openFileOutput() do not leave any option but to save the resulting file in the root of getFilesDir(). What if I need some directory structure to keep things in order? In addition, my application must handle more than one picture, so I would like to have the filename generated so I can refer to it later on.
See, it is easy to capture a photo with the camera and save it to public image area (via MediaStore) on the Droid device. It is also easy to manipulate (query, update, delete) media from MediaStore. Interestingly, inserting camera picture to MediaStore genreates a filename which appears to be unique. It is also easy to create private File for an application with a directory structure. The crux of the "Capturea camera picture and save it to internal memory" problem is that you can't do so directly because Android prevents ContentResolver to use Media.INTERNAL_CONTENT_URI, and because private app files are by definition not accessible via the (outside) Camera activity.
Finally I adopted the following strategy:
Start the Camera activity for result from my app with the Intent to capture image.
When returning to my app, insert capture to the MediaStore.
Query the MediaStore to obtain generated image file name.
Create a truly internal file onto whatever path relative to private application data folder using Context.getDir().
Use an OutputStream to write Bitmap data to this private file.
Delete capture from MediaStore.
(Optional) show an ImageView of the capture in my app.
Here is the code starting the cam:
public void onClick (View v)
{
ContentValues values = new ContentValues ();
values.put (Media.IS_PRIVATE, 1);
values.put (Media.TITLE, "Xenios Mobile Private Image");
values.put (Media.DESCRIPTION, "Classification Picture taken via Xenios Mobile.");
Uri picUri = getActivity ().getContentResolver ().insert (Media.EXTERNAL_CONTENT_URI, values);
//Keep a reference in app for now, we might need it later.
((XeniosMob) getActivity ().getApplication ()).setCamPicUri (picUri);
Intent takePicture = new Intent (MediaStore.ACTION_IMAGE_CAPTURE);
//May or may not be populated depending on devices.
takePicture.putExtra (MediaStore.EXTRA_OUTPUT, picUri);
getActivity ().startActivityForResult (takePicture, R.id.action_camera_start);
}
And here is my activity getting cam result:
#Override
protected void onActivityResult (int requestCode, int resultCode, Intent data)
{
super.onActivityResult (requestCode, resultCode, data);
if (requestCode == R.id.action_camera_start)
{
if (resultCode == RESULT_OK)
{
Bitmap pic = null;
Uri picUri = null;
//Some Droid devices (as mine: Acer 500 tablet) leave data Intent null.
if (data == null) {
picUri = ((XeniosMob) getApplication ()).getCamPicUri ();
} else
{
Bundle extras = data.getExtras ();
picUri = (Uri) extras.get (MediaStore.EXTRA_OUTPUT);
}
try
{
pic = Media.getBitmap (getContentResolver (), picUri);
} catch (FileNotFoundException ex)
{
Logger.getLogger (getClass ().getName ()).log (Level.SEVERE, null, ex);
} catch (IOException ex)
{
Logger.getLogger (getClass ().getName ()).log (Level.SEVERE, null, ex);
}
//Getting (creating it if necessary) a private directory named app_Pictures
//Using MODE_PRIVATE seems to prefix the directory name provided with "app_".
File dir = getDir (Environment.DIRECTORY_PICTURES, Context.MODE_PRIVATE);
//Query the MediaStore to retrieve generated filename for the capture.
Cursor query = getContentResolver ().query (
picUri,
new String [] {
Media.DISPLAY_NAME,
Media.TITLE
},
null, null, null
);
boolean gotOne = query.moveToFirst ();
File internalFile = null;
if (gotOne)
{
String dn = query.getString (query.getColumnIndexOrThrow (Media.DISPLAY_NAME));
String title = query.getString (query.getColumnIndexOrThrow (Media.TITLE));
query.close ();
//Generated name is a ".jpg" on my device (tablet Acer 500).
//I prefer to work with ".png".
internalFile = new File (dir, dn.subSequence (0, dn.lastIndexOf (".")).toString () + ".png");
internalFile.setReadable (true);
internalFile.setWritable (true);
internalFile.setExecutable (true);
try
{
internalFile.createNewFile ();
//Use an output stream to write picture data to internal file.
FileOutputStream fos = new FileOutputStream (internalFile);
BufferedOutputStream bos = new BufferedOutputStream (fos);
//Use lossless compression.
pic.compress (Bitmap.CompressFormat.PNG, 100, bos);
bos.flush ();
bos.close ();
} catch (FileNotFoundException ex)
{
Logger.getLogger (EvaluationActivity.class.getName()).log (Level.SEVERE, null, ex);
} catch (IOException ex)
{
Logger.getLogger (EvaluationActivity.class.getName()).log (Level.SEVERE, null, ex);
}
}
//Update picture Uri to that of internal file.
((XeniosMob) getApplication ()).setCamPicUri (Uri.fromFile (internalFile));
//Don't keep capture in public storage space (no Android Gallery use)
int delete = getContentResolver ().delete (picUri, null, null);
//rather just keep Uri references here
//visit.add (pic);
//Show the picture in app!
ViewGroup photoLayout = (ViewGroup) findViewById (R.id.layout_photo_area);
ImageView iv = new ImageView (photoLayout.getContext ());
iv.setImageBitmap (pic);
photoLayout.addView (iv, 120, 120);
}
else if (resultCode == RESULT_CANCELED)
{
Toast toast = Toast.makeText (this, "Picture capture has been cancelled.", Toast.LENGTH_LONG);
toast.show ();
}
}
}
Voila! Now we have a truly application private picture file, which name has been generated by the Droid device. And nothing is kept in the public storage area, thus preventing accidental picture manipulation.
here is my working code to save a captured image from the camera to app internal storage:
first, create the file with the desired filename. in this case it is "MyFile.jpg", then start the activity with the intent below. you're callback method(onActivityResult), will be called once complete. After onActivityResult has been called your image should be saved to internal storage. key note: the mode used in openFileOutput needs to be global.. Context.MODE_WORLD_WRITEABLE works fine, i have not tested other modes.
try {
FileOutputStream fos = openFileOutput("MyFile.jpg", Context.MODE_WORLD_WRITEABLE);
fos.close();
File f = new File(getFilesDir() + File.separator + "MyFile.jpg");
startActivityForResult(
new Intent(MediaStore.ACTION_IMAGE_CAPTURE)
.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(f))
, IMAGE_CAPTURE_REQUEST_CODE);
}
catch(IOException e) {
}
and in the activity result method:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == IMAGE_CAPTURE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
Log.i(TAG, "Image is saved.");
}
}
to retrieve your image:
try {
InputStream is = openFileInput("MyFile.jpg");
BitmapFactory.Options options = new BitmapFactory.Options();
//options.inSampleSize = 4;
Bitmap retrievedBitmap = BitmapFactory.decodeStream(is, null, options);
}
catch(IOException e) {
}
The camera apparently doesn't support writing to internal storage.
Unfortunately this is not mentioned in the documentation.
MediaProvider.java has the following code:
private String generateFileName(boolean internal,
String preferredExtension, String directoryName)
{
// create a random file
String name = String.valueOf(System.currentTimeMillis());
if (internal) {
throw new UnsupportedOperationException(
"Writing to internal storage is not supported.");
// return Environment.getDataDirectory()
// + "/" + directoryName + "/" + name + preferredExtension;
} else {
return Environment.getExternalStorageDirectory()
+ "/" + directoryName + "/" + name + preferredExtension;
}
}
So writing to internal storage has been intentionally disabled for the time being.
Edit - I think you can use binnyb's method as a work-around, but I wouldn't recommend it; I'm not sure if this will continue to work on future versions. I think the intention is to disallow writing to internal storage for media files.
I filed a bug in the Android issue tracker.
Edit - I now understand why binnyb's method works. The camera app is considered to be just another application. It can't write to internal storage if it doesn't have permissions. Setting your file to be world-writable gives other applications permission to write to that file.
I still don't think that this is a very good idea, however, for a few reasons:
You don't generally want other apps writing to your private storage.
Internal storage is quite limited on some phones, and raw camera images are quite large.
If you were planning on resizing the image anyway, then you can read it from external storage and write it yourself to your internal storage.

Categories

Resources