I have an application for API26+
With Android 10 and above (API29+) should be used MediaStore to access files, instead of Environment.getExternalStoragePublicDirectory
normally making of new approach would be maked in creation of if-block
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
// deprecated code
} else {
// new approach
}
But:
MediaStorage use Uri to request a file. API28 and below works with File or String as path to file
With MediaStore manually creation of a folder is not needed. API 28 and below should create a folder if not exists.
Deleting of Files requires User interaction with MediaStore.
CRUD:
Create: To write data in File for any API is OutputStream required. That could be reached with if else
Read: How to create general approach to read a File, where API 29+ needs Uri, and API28- needs File|String to access a file?
Update: first Read to check if exists, then Create to get OutputStream
Delete: How to create a batch confirmation with MediaStore?
Is it possible to work with MediaStorage with API26+?
If yes, how? Many properties are added first in API29
After some own research and testing i have found the way to work with MediaStore and on old devices in same time.
First of all some helper classes needed:
With FileType we can support different file types in application at same time.
public enum FileType {
File(Environment.DIRECTORY_DOCUMENTS, Constants.VERSION_29_ABOVE ? MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL) : null, "text/plain"),
Download(Environment.DIRECTORY_DOWNLOADS, Constants.VERSION_29_ABOVE ? MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL) : null, "text/plain"),
Image(Environment.DIRECTORY_PICTURES, Constants.VERSION_29_ABOVE ? MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL) : null, "image/jpeg"),
Audio(Environment.DIRECTORY_MUSIC, Constants.VERSION_29_ABOVE ? MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL) : null, "audio/mpeg"),
Video(Environment.DIRECTORY_MOVIES, Constants.VERSION_29_ABOVE ? MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL) : null, "video/mpeg");
private final String directory;
private final Uri contentUri;
private String mimeType;
FileType(String directory, Uri contentUri, String mimeType) {
this.directory = directory;
this.contentUri = contentUri;
this.mimeType = mimeType;
}
public String getDirectory() {
return directory;
}
public Uri getContentUri() {
return contentUri;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
}
with setMimeType we can change current file extension and still use other settings
And we need simple callback class to get different results
public interface ObjectCallback<T> {
void result(T object);
}
When we want write some data in a file, we need an OutputStream
With internal storage, the way to get a file has not changed
/**
* opens OutputStream to write data to file
*
* #param context activity context
* #param fileName relative file name
* #param fileType file type to get folder specific values for access
* #param fileStream callback with file output stream to requested file
* #return true if output stream successful opened, false otherwise
*/
public boolean openOutputStream(Context context, String fileName, FileType fileType, ObjectCallback<OutputStream> fileStream) {
File internalFolder = context.getExternalFilesDir(fileType.getDirectory());
File absFileName = new File(internalFolder, fileName);
try {
FileOutputStream fOut = new FileOutputStream(absFileName);
fileStream.result(fOut);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
By external storage, the way to access a file on API 28- and API 29+ is completely different.
On API 28- we could just use a file for it.
On API 29+ we shoould first check, if file already exists. If it (say text.txt) exists and we would make ContentResolver.insert, it would create text-1.txt and in worst case (endless loop) in could quick fill whole smartphone storage.
/**
* #param context application context
* #param fileName relative file name
* #return uri to file or null if file not exist
*/
public Uri getFileUri(Context context, String fileName) {
// remove folders from file name
fileName = fileName.contains("/") ? fileName.substring(fileName.lastIndexOf("/") + 1) : fileName;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
// Deprecated in API 29
File storageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), Constants.PUBLIC_STORAGE_FOLDER);
File file = new File(storageDir, fileName);
return file.exists() ? Uri.fromFile(file) : null;
} else {
String folderName = Environment.DIRECTORY_PICTURES + File.separator + Constants.PUBLIC_STORAGE_FOLDER + File.separator;
// get content resolver that can interact with public storage
ContentResolver resolver = context.getContentResolver();
String selection = MediaStore.MediaColumns.RELATIVE_PATH + "=?";
String[] selectionArgs = new String[]{folderName};
Cursor cursor = resolver.query(MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL), null, selection, selectionArgs, null);
Uri uri = null;
if (cursor.getCount() > 0) {
while (cursor.moveToNext()) {
String itemFileName = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME));
if (itemFileName.equals(fileName)) {
long id = cursor.getLong(cursor.getColumnIndex(MediaStore.MediaColumns._ID));
uri = ContentUris.withAppendedId(MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL), id);
break;
}
}
}
cursor.close();
return uri;
}
}
/**
* opens OutputStream to write data to file
*
* #param context activity context
* #param fileName relative file name
* #param fileType file type to get folder specific values for access
* #param fileStream callback with file output stream to requested file
* #return true if output stream successful opened, false otherwise
*/
public boolean openOutputStream(Context context, String fileName, FileType fileType, ObjectCallback<OutputStream> fileStream) {
// remove folders from file name
fileName = fileName.contains("/") ? fileName.substring(fileName.lastIndexOf("/") + 1) : fileName;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
File storageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), Constants.PUBLIC_STORAGE_FOLDER);
if (!storageDir.exists() && !storageDir.mkdir()) {
// directory for file not exists and not created. return false
return false;
}
File file = new File(storageDir, fileName);
try {
FileOutputStream fOut = new FileOutputStream(file);
fileStream.result(fOut);
} catch (Exception e) {
e.printStackTrace();
return false;
}
} else {
// get content resolver that can interact with public storage
ContentResolver resolver = context.getContentResolver();
// always check first if file already exist
Uri existFile = getFileUri(context, fileName);
if (existFile == null) {
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.MIME_TYPE, fileType.getMimeType());
// absolute folder name
values.put(MediaStore.MediaColumns.RELATIVE_PATH, fileType.getDirectory() + File.separator + Constants.PUBLIC_STORAGE_FOLDER);
// file name
values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
// create uri to the file. If folder not exists, it would be created automatically
Uri uri = resolver.insert(fileType.getContentUri(), values);
// open stream from uri and write data to file
try (OutputStream outputStream = resolver.openOutputStream(uri)) {
fileStream.result(outputStream);
// end changing of file
// when this point reached, no exception was thrown and file write was successful
values.put(MediaStore.MediaColumns.IS_PENDING, false);
resolver.update(uri, values, null, null);
} catch (Exception e) {
e.printStackTrace();
if (uri != null) {
// Don't leave an orphan entry in the MediaStore
resolver.delete(uri, null, null);
}
return false;
}
} else {
// open stream from uri and write data to file
try (OutputStream outputStream = resolver.openOutputStream(existFile)) {
fileStream.result(outputStream);
} catch (Exception e) {
e.printStackTrace();
// Don't leave an orphan entry in the MediaStore
resolver.delete(existFile, null, null);
return false;
}
}
}
return true;
}
In same way could be opened InputStream on all devices and different storage.
To delete file:
internal could be still deleted with file.delete on every API
external storage with API29+ requires user interaction to delete some file
FileProvider
To use Intent for showing some picture, crop picture or whatever, where we use setDataAndType
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(photoUri, "image/*");
// allow to read the file
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
we need in some cases the FileProvider. Specially:
To show external file created with the app, the photoUri would look this way
Uri photoUri = Constants.VERSION_29_ABOVE ? uriToFile :
FileProvider.getUriForFile(context,
context.getApplicationContext().getPackageName() + ".provider",
new File(uriToFile.getPath()));
Related
I am working on an application that allows users to upload files. I want to keep the uploaded files organized into pre-created folders named for the group that the user belongs to. I can't seem to find a way to make the path editable so that I can pass the group's name into the method as a parameter and have the file stored in that directory.
Here's my latest attempt that results in a "Failed to store file file] with root cause" exception.
#Service
public class StorageServiceImpl implements StorageService {
#Value("${upload.path}")
private Path path;
public void uploadFile(MultipartFile file,String contentName, String groupName){
//make so that files are stored in path/groupname
this.path = Paths.get(this.path.toString() +"/"+groupName +"/");
String filename = contentName+"-"+StringUtils.cleanPath(file.getOriginalFilename());
System.out.println("\n\n\n\n\n"+filename + "\n\n\n");
try {
if (file.isEmpty()) {
throw new StorageException("Failed to store empty file");
}
if (filename.contains("..")) {
// This is a security check
throw new StorageException(
"Cannot store file with relative path outside current directory "
+ filename);
}
try (InputStream inputStream = file.getInputStream()) {
System.out.println("\n\n\n\n\n"+this.path.resolve(filename) + "\n\n\n");
Files.copy(inputStream, this.path.resolve(filename), StandardCopyOption.REPLACE_EXISTING);
}
}catch (IOException e) {
String msg = String.format("Failed to store file %s", file.getName());
throw new StorageException(msg, e);
}
}
}
Note: If the directory of the groupName is created before this method runs (as I intend to have it created when the group is created) then the method attempts to store the file in another directory of the same name inside that directory such as:
backend/src/main/webapp/WEB-INF/TestGroup/TestGroup/test.jpg
See how TestGroup shows up twice
Instead of doing this, here you are trying to inject value to a varible of type Path.
#Value("${upload.path}")
private Path path;
how about reading the value to a String variable and storing it as a File and then using it to upload like below,
#Value("${upload.path}")
private String path;
And then using it
public void uploadFile( MultipartFile file, String contentName, String groupName )
{
String filename = contentName + "-" + StringUtils.cleanPath( file.getOriginalFilename() );
// use a File object here
File uploadFilePath = Paths.get( new File( path ).getPath() + "/" + groupName + "/" + filename ).toFile();
try
{
try( InputStream in = file.getInputStream(); OutputStream out = new FileOutputStream( uploadFilePath ) )
{
// Leverage the support of Spring's FileCopyUtils Here
FileCopyUtils.copy( in, out );
}
catch( IOException ex )
{
throw new RuntimeException( ex );
}
}
catch( IOException e )
{
String msg = String.format( "Failed to store file %s", file.getName() );
throw new StorageException( msg, e );
}
}
I got it working by using the absolutePath method from the private Path path to generate the string.
public void uploadFile(MultipartFile file,String contentName, String groupName){
//make so that files are stored in path/groupname
String filename = contentName+"-"+StringUtils.cleanPath(file.getOriginalFilename());
File uploadFilePath = Paths.get(this.path.toAbsolutePath() + "/" + groupName+"/" + filename).toFile();
try {
if (file.isEmpty()) {
throw new StorageException("Failed to store empty file");
}
if (filename.contains("..")) {
// This is a security check
throw new StorageException(
"Cannot store file with relative path outside current directory "
+ filename);
}
try (InputStream inputStream = file.getInputStream();OutputStream out = new FileOutputStream(uploadFilePath);) {
FileCopyUtils.copy(inputStream,out);
}
}catch (IOException e) {
String msg = String.format("Failed to store file %s", file.getName());
throw new StorageException(msg, e);
}
}
I've now tried since a long time to make a cross app sharing intent from assets folder for different types of files, but there is always a problem working. I went on a lot of different types of solutions such as:
Try to use a custom content provider (here, or here, ..)
Try to save the file on local storage (example here)
and others but as it didn't worked I don't have links anymore..
Right now, here is my working solution for sharing an audio file to whatsapp and messenger only, all other app are failing).
My function to create a new file on app storage:
public String getNewPathFromSbElem(SbElem sbElem, String fileName) {
AssetManager assetManager = getAssets();
String path = sbElem.soundPath;
String newName = fileName;
String newPath = getExternalFilesDir(null).getAbsolutePath() + File.separator + newName;
InputStream in = null;
OutputStream out = null;
File outFile;
File deleteFile = new File(newPath);
if(deleteFile.exists()) {
deleteFile.delete();
}
try {
in = assetManager.open(path);
outFile = new File(getExternalFilesDir(null), newName);
out = new FileOutputStream(outFile);
copyFile(in, out);
} catch (IOException ex) {
ex.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
if (out != null) {
try {
out.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
return newPath;
}
My 2 functions to share on whatsapp or messenger:
public void shareOnWhatsApp(SbElem sbElem) {
final String newPath = getNewPathFromSbElem(sbElem, "_share_temp_file.mp3");
Intent whatsappIntent = new Intent(Intent.ACTION_SEND);
whatsappIntent.setPackage("com.whatsapp");
whatsappIntent.setType("audio/mp3");
whatsappIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(newPath));
whatsappIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
try {
startActivity(whatsappIntent);
} catch (android.content.ActivityNotFoundException ex) {
Log.d("error", "WhatsApp not installed");
}
}
public void shareOnMessenger (SbElem sbElem) {
final String newPath = getNewPathFromSbElem(sbElem, "_share_temp_file.mp3");
final File newFile = new File(newPath);
final Uri newUri = FileProvider.getUriForFile(this, getString(R.string.file_provider_authority), newFile);
final Integer SHARE_TO_MESSENGER_REQUEST_CODE = 1;
String mimeType = "audio/*";
ShareToMessengerParams shareToMessengerParams = ShareToMessengerParams.newBuilder(newUri, mimeType).build();
MessengerUtils.shareToMessenger(this, SHARE_TO_MESSENGER_REQUEST_CODE, shareToMessengerParams);
}
The thing is, I would like to be able to share from my asset folder a .mp3, a .jpg, a .png, to gmail, to whatsapp, to slack, or any type of app that supports this extension...
So as almost all the 1000 questions / articles online regarding sharing assets are answered by using a custom content provider, I tried the following sharing function:
public void shareBasic () {
// I added the test.jpg in the root of my asset folder
// Tried with content / file / 2 or 3 '/', with package name and with assets / ...
Uri theUri = Uri.parse("content:///com.MY_PACKAGE_NAME/test.jpg");
//Uri theUri = Uri.parse("content:///assets/test.jpg");
//Uri theUri = Uri.parse("file:///com.MY_PACKAGE_NAME/test.jpg");
//Uri theUri = Uri.parse("file:///asset.jpg");
Intent theIntent = new Intent(Intent.ACTION_SEND);
// Tried with jpeg / png / jpg ...
theIntent.setType("image/*");
theIntent.putExtra(Intent.EXTRA_STREAM, theUri);
theIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(theIntent);
}
And here is my Android manifest:
<provider
android:name="MY_PACKAGE_NAME.MyContentProvider"
android:authorities="MY_PACKAGE_NAME"
android:grantUriPermissions="true"
android:exported="true" />
And the file provider (almost the same on every tutorial)
public class MyContentProvider extends ContentProvider {
#Override
public boolean onCreate() {
return true;
}
#Override
public Cursor query(#NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
#Override
public Cursor query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal )
{
// TODO: Implement this method
return super.query( uri, projection, selection, selectionArgs, sortOrder, cancellationSignal );
}
#Nullable
#Override
public String getType(#NonNull Uri uri) {
return null;
}
#Nullable
#Override
public Uri insert(#NonNull Uri uri, ContentValues values) {
return null;
}
#Override
public int delete(#NonNull Uri uri, String selection, String[] selectionArgs) {
return 0;
}
#Override
public int update(#NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
#Override
public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException {
AssetManager am = getContext().getAssets();
String fileName = uri.getLastPathSegment();
if(fileName == null)
throw new FileNotFoundException();
AssetFileDescriptor fileDescriptor = null;
try {
fileDescriptor = am.openFd(fileName);
} catch (IOException e) {
e.printStackTrace();
}
return fileDescriptor;
}
}
I guess I'm doing huge mistakes as sharing an file from asset folder shouldn't be such a struggle, any ideas ?
For your ContentProvider solution:
You need to have your query() function support the OpenableColumns
You need to have your getType() function return the actual MIME type
Your Intent needs to use a concrete MIME type, not a wildcard
See this old sample project for a demonstration.
I am trying to create a option for the user to send the file from my application via email only. The file is internal to the application and is accessible via the FileProvider.
This is the contentURI looks like content://packagename.files/files/somefile.ext
Here as you can see that I am giving the user to share the file to PicsArt, Google Drive, OneDrive and EMail.
I am able to share the content to the first three clients successfully as they very specific applications. But when it comes to Email, I need to user to pick the client from the applications he has installed in his mobile.
Here are 2 set of codes I have created:
Code Option 1:
Intent EMail = ShareCompat.IntentBuilder.from(this)
.setType("message/rfc822")
.setSubject("Emailing: File Attached")
.setText("Hello")
.setStream(contentUri)
.setChooserTitle("Send via EMail").getIntent();
startActivity(Intent.createChooser(EMail, "Send via EMail"));
Above code shows me a chooser where there are many applications which can handle the files as shown in the image below.
This one works fine if I select any email client application or any other application.
But the problem with this is that there is an option for the user to select any application, which is not the desired behavior of the application. So, I modified the code as below:
final Intent _Intent = new Intent(Intent.ACTION_SENDTO);
_Intent.setType("text/html");
_Intent.setData(Uri.parse("mailto:"));
_Intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
_Intent.putExtra(Intent.EXTRA_STREAM, contentUri);
_Intent.putExtra(android.content.Intent.EXTRA_SUBJECT,
"Emailing: File Attached");
_Intent.putExtra(android.content.Intent.EXTRA_TEXT,
"Hello");
startActivity(Intent.createChooser(_Intent, "Send via EMail"));
Here is the outcome of the code:
But, now the problem here is that I am not able to send the file from the content provider (FileProvider). The email client shows the message as below after selecting:
It is simply not attaching the file to the email in any client in the above list.
I will be greatfull, if anyone can help me out here. I think that, I have tried all the possible scenarios here, by changing the mime-type, setting content in different manner setting data setting stream etc, but not able to get the desired outcome.
Please let me know in case you need any other details on this.
Thanks again in advance.
You need to write the ContentProvider which will provide the InputStream to the client to which you have passed the ContentUri or may be you can directly provide the file path if its present in the SdCard or the Internal storage because you will need to handle the uri's and pass the InputStream . Note: ExtraStream is best for files that are not in device that is which is to be accessed from internet.
public class SampleContentProvider extends ContentProvider implements ContentProvider.PipeDataWriter<InputStream> {
static final UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
//Uri matcher for different
}
/**
* Database specific constant declarations
*/
private SQLiteDatabase db;
#Override
public boolean onCreate() {
return true;
}
#Override
public Uri insert(Uri uri, ContentValues values) {
throw new SQLException("Insert operation not supported for " + uri);
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
//condition just for files. You can try something else
if (uri.toString().contains("files")) {
//you get the file name
String lastSegment = uri.getLastPathSegment();
if (projection == null) {
projection = new String[]{OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE};
}
File file = //Code to read the file as u have the directory, just get the file from the file name obtained from the uri
if (null == file) {
throw new IllegalArgumentException("Unknown File for Uri " + uri);
}
String[] cols = new String[projection.length];
Object[] values = new Object[projection.length];
int i = 0;
for (String col : projection) {
if (OpenableColumns.DISPLAY_NAME.equals(col)) {
cols[i] = OpenableColumns.DISPLAY_NAME;
values[i++] = //file name;
} else if (OpenableColumns.SIZE.equals(col)) {
cols[i] = OpenableColumns.SIZE;
values[i++] = //file size;
}
}
cols = copyOf(cols, i);
values = copyOf(values, i);
final MatrixCursor cursor = new MatrixCursor(cols, 1);
cursor.addRow(values);
return cursor;
}
return super.query(uri, projection, selection, selectionArgs, sortOrder);
}
#Override
public String getType(Uri uri) {
return null;
}
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return super.delete(uri, selection, selectionArgs);
}
private static String[] copyOf(String[] original, int newLength) {
final String[] result = new String[newLength];
System.arraycopy(original, 0, result, 0, newLength);
return result;
}
private static Object[] copyOf(Object[] original, int newLength) {
final Object[] result = new Object[newLength];
System.arraycopy(original, 0, result, 0, newLength);
return result;
}
#Nullable
#Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
File file = //read the file
if (file != null) {
try {
StrictMode.ThreadPolicy tp = StrictMode.ThreadPolicy.LAX;
StrictMode.setThreadPolicy(tp);
InputStream in = //Code to get the inputstream;
// Start a new thread that pipes the stream data back to the caller.
return openPipeHelper(uri, null, null, in, this);
} catch (IOException e) {
FileNotFoundException fnf = new FileNotFoundException("Unable to open " + uri);
throw fnf;
}
}
throw new IllegalArgumentException("Unknown URI " + uri);
}
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return super.update(uri, values, selection, selectionArgs);
}
#Override
public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, String mimeType,
Bundle opts, InputStream args) {
// Transfer data from the asset to the pipe the client is reading.
byte[] buffer = new byte[8192];
int n;
FileOutputStream fout = new FileOutputStream(output.getFileDescriptor());
try {
while ((n = args.read(buffer)) >= 0) {
fout.write(buffer, 0, n);
}
} catch (IOException e) {
} finally {
try {
args.close();
} catch (IOException e) {
}
try {
fout.close();
} catch (IOException e) {
}
}
}
}
I have decided to copy the file from internal app storage to external app storage (not external public storage) and sharing the file from there. I am bit surprised though as the FileProvider is capable of sharing the files from the internal file storage with anything and every thing in the system but fails to do so when I want to filter the Intents which are email clients only.
It was kind of difficult for me to implement a custom provider at beginner's level.
try this snippet.
Intent testIntent = new Intent(Intent.ACTION_VIEW);
Uri data = Uri.parse("mailto:?subject=" + "Feedback" + "&body=" + "Write Feedback here....." + "&to=" + "someone#example.com");
testIntent.setData(data);
startActivity(testIntent);
I have a function attached to a button that looks like this:
public void viewSnappies(View z) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(Util.getOutputMediaFile().mediaStorageDir().getPath()));
startActivity(intent);
}
In my Util Class, I have:
public final class Util {
/**
* Create a File for saving an image
*/
// http://developer.android.com/guide/topics/media/camera.html#saving-media
public static File getOutputMediaFile(Context context) {
File mediaStorageDir = null;
// To be safe, you should check that the SDCard is mounted
// using Environment.getExternalStorageState() before doing this.
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "snappiesticker");
} else {
ContextWrapper cw = new ContextWrapper(context);
mediaStorageDir = cw.getDir("imageDir", Context.MODE_PRIVATE);
}
// This location works best if you want the created images to be shared
// between applications and persist after your app has been uninstalled.
// Create the storage directory if it does not exist
if (!mediaStorageDir.exists()) {
if (!mediaStorageDir.mkdirs()) return null;
}
// Create a media file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
File mediaFile;
mediaFile = new File(mediaStorageDir.getPath() + File.separator + "IMG_" + timeStamp + ".jpg");
return mediaFile;
}
}
Right now the button is not working as I intend it to (error in the new Intent line). Its not resolving because I am putting the wrong thing into Uri.parse(). I want it to resolve to and use the mediaStorageDir defined in the getOutputMediaFile method given in the Util class.
How do I pass this variable correctly?
I am doing a simple application that loads and saves files in java. I am trying to port it over to Android and am having trouble getting it to see the file.
The file path I am currently using is
private static final String SAVE_FILE_PATH = "data/save";
Here is the function that loads the data from the file:
public void loadData() throws FileNotFoundException {
File file = new File(SAVE_FILE_PATH);
Scanner scanner;
if (file.exists()) {
scanner = new Scanner(new FileInputStream(file));
try {
while (scanner.hasNextLine()) {
allPlayers.add(new Player(scanner.nextLine()));
}
} finally {
scanner.close();
}
}
else {
System.out.println("No file found");
}
} finally {
scanner.close();
}
}
}
While getExternalStorageDirectory() gets you the path to the SD card, consider using Activity.getExternalFilesDir() which will return (and create if necessary) a directory that's nominally private to your application. It also has the advantage that it will be auto-deleted for you if the application is uninstalled. This is new in API 8, so you might not want to use it if you're supporting older devices.
Otherwise, you'll have to follow ρяσѕρєя K's advice. Don't forget to create the storage directory you want to use. My code typically looks like this:
/**
* Utility: Return the storage directory. Create it if necessary.
*/
public static File dataDir()
{
File sdcard = Environment.getExternalStorageDirectory();
if( sdcard == null || !sdcard.isDirectory() ) {
// TODO: warning popup
Log.w(TAG, "Storage card not found " + sdcard);
return null;
}
File datadir = new File(sdcard, "MyApplication");
if( !confirmDir(datadir) ) {
// TODO: warning popup
Log.w(TAG, "Unable to create " + datadir);
return null;
}
return datadir;
}
/**
* Create dir if necessary, return true on success
*/
public static final boolean confirmDir(File dir) {
if( dir.isDirectory() ) return true;
if( dir.exists() ) return false;
return dir.mkdirs();
}
Now use this to specify your save file:
File file = new File(dataDir(), "save");
Scanner scanner;
if (file.exists()) {
// etc.
}