I need to user to choose a file from storage. Once user chooses the file I can't do anything with it because it comes back with an exception "File does not exist", although it most certainly does. Permissions are granted and I even take persistent URI permission.
This sample code is not the prettiest, I know but it still shouldn't give me that excpetion I think. Did I write something wrong or that could cause this problem?
Currently this sample code doesn't throw an exception but if statement fails and logs "File does not exist". I need it to pass if statement and call openMap(). If I were to remove the if statement I would get org.mapsforge.map.reader.header.MapFileException: file does not exist: content:/com.android.externalstorage.documents/document/primary%3Aestonia.map
final ActivityResultLauncher<Intent> sARL = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback<ActivityResult>() {
#Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_OK){
Intent data = result.getData();
assert data != null;
Uri uri = data.getData();
File oFile = new File(uri.getPath());
getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
if (new File(uri.getPath()).exists()){
openMap(oFile);
}else{
Log.w("File", "File does not exist");
}
}
}
});
public void openFileDialog(){
Intent data = new Intent(Intent.ACTION_OPEN_DOCUMENT);
data.setType("*/*");
data = Intent.createChooser(data, "Choose tile");
sARL.launch(data);
}
Try below code
File oFile = new File(data.getData().getPath());
if (oFile.exists()){
Toast.makeText(this, "exist: "+oFile.exists(), Toast.LENGTH_SHORT).show(); //returns true
openMap(oFile);
}else{
Log.w("File", "File does not exist");
}
Let me know if the problem not solved yet.
The problem was with the Uri. If I hard code path to the file then everything is fine, but with file choosing intent I can't actually access the real file path, as far as I know. File contents can still be read but I needed the path.
File oFile = new File(Environment.getExternalStorageDirectory() + filePath)
This did the trick and I got the actual file loaded.
Related
My problem is that I do not manage to create a directory on a SD card that is plugged in an Android portable device.
Below is the Java code I am trying to get to work: I am trying to create the directory sable under /storage/BF4F-1107/:
public class AnActivity extends AppCompatActivity
{
private static final int N_CREATE_DIRECTORY = 1;
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// SD card
String s_sdCardStorage = "/storage/BF4F-1107/";
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("file/*");
intent.putExtra(Intent.EXTRA_TITLE, s_destFilePath);
startActivityForResult(intent, N_CREATE_DIRECTORY);
// HERE
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
if(requestCode == N_CREATE_DIRECTORY)
{
if(data != null)
{
Uri uri = data.getData();
DocumentFile docFileSDCardStorage = DocumentFile.fromSingleUri(this, uri);
try
{
DocumentFile docFileDir = docFileSDCardStorage.createDirectory("sable");
}
catch(UnsupportedOperationException exn)
{
System.out.println(exn.getMessage());
}
}
}
}
}
What happens is:
the code in onCreate() is executed.
Then the program blocks at // HERE.
On the portable device, appears a "dialog" which shows /storage/BF4F-1107/ and a button Save which I "press".
Once "pressed", the code in onActivityResult() is executed.
But the result is that the directory sable under /storage/BF4F-1107/ is not created.
And the execution path goes through the catch clause, the exception UnsupportedOperationException is raised and null is printed at System.out.println(exn.getMessage());.
Edit 2: An empty file _storage_BF4F-1107_ is created under /storage/BF4F-1107/.
Can you help me make this code work?
Additionally, I would like the directory sable to be created silently.
I do not want to user to have to touch "Save".
I am trying to use the Storage Access Framework (https://developer.android.com/training/data-storage/shared/documents-files) because the mkdirs method of the java.io.File class doesn't work (I get permission denied exceptions) when I try to create a directory on the SD card.
Edit: my Android version is 6.0.1
Thank you.
Use ACTION_OPEN_DOCUMENT_TREE to let the user choose de SD card.
After that you can create as many files and directorys in the choosen directory.
If you only want to create one file with SAF use ACTION_CREATE_DOCUMENT where the user chooses the location and file name.
I'm sharing image file to WhatsApp.
Whatsapp can't read private file so I saved it into public directory.
I want --> when a user shares the image it should be deleted from the storage.
try {
String uniqueName = System.currentTimeMillis() + "";
File tempFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "ABBM-temp");
if (!tempFile.exists()) {
tempFile.mkdirs();
}
tempFile = new File(tempFile + uniqueName + ".png");
FileOutputStream fOut = new FileOutputStream(tempFile);
b.compress(Bitmap.CompressFormat.PNG, 100, fOut);
fOut.flush();
fOut.close();
final Intent intent = new Intent(android.content.Intent.ACTION_SEND);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(tempFile));
intent.setType("image/png");
startActivity(Intent.createChooser(intent, "Share image via"));
//tempFile.delete();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(getApplicationContext(), "Image not shared, please grant storage permission", Toast.LENGTH_SHORT).show();
}
The above code opens sharing intent but deletes file in background and I'm unable to share the file.
Share file and delete it.
Thank you..
Your code is a bit clunky and is guaranteed to have errors in the latest versions of Android. Here are some observations:
Solution 1
1. Use a File Provider and write to private storage area
I know that you are writing to the main storage, so other apps can read the file, but it shouldn't be done that way. Use a file provider and get a Uri from that. Here is the documentation: File Provider Documentation. When you have your file provider set-up(believe me it's easy, you just have to add a snippet to your manifest and add a xml path) you can write privately to the memory. Like this:
FileOutputStream fOut = openFileOutput("the_name_of_file_here", Context.MODE_PRIVATE);
b.compress(Bitmap.CompressFormat.PNG, 100, fOut);
fOut.flush();
fOut.close();
Then using a getUriForFile you can get a Uri usable by any app that you give access to. Like this:
File file = new File(getFilesDir(), "the_name_of_file_here");
Uri uri = getUriForFile(this, "com.yourpackagename.fileprovider", file);
2. Use ShareCompat
When you have set-up the FileProvider, I suggest to you that for sharing files use ShareCompat instead of a regular sharing activity. ShareCompat has the advantage of being officially supported by Google and you can also share a multitude of files with it. Without having to grant URI_PERMISSION to the other app. Here's an example:
Intent shareIntent = ShareCompat.IntentBuilder.from(this)
.setType(type) //This is the MIME type
.setStream(uri) //This is the uri we got earlier
.getIntent();
shareIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(shareIntent); //Starting the activity
I advise you to take a look here at this blog: Getting Started with Share-Compat for more information. Hope this helps.
Solution 2
Traditional Solution:
If you still want to use your function, because of preference or because it suits your app better, here is one thing you can do:
Instead of using startActivity(Intent) use startActivityForResult(Intent, int). With that what you can do is pass an integer value which will work as an id. Then when the file is shared and you return back to your app, the onActivityResult() function will be fired. It will have a requestCode. Like this:
//Create your share intent as you do in your code, then:
startActivityForResult(intent, 512); //I passed 512 you can pass anything you want
Then override this function:
#Override
public void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
//Check if it is from the same code, if yes delete the temp file
if(requestCode == 512) {
tempFile.delete();
}
}
What i'm trying to achieve is to delete a file on the sd card, i tried the file.delete method which didn't work because sd cards are read only now.
So i read a post about SAF (Storage Access Framework) to gain sd card write access by storing the treeUri we get in onActivityResult.
File deleting works fine now, but when i start the intent Intent.ACTION_OPEN_DOCUMENT_TREE sometimes it returns the recent folder which is empty and the way to show the files on the sdcard is to click on the overflow icon and then select show SDCARD or Internal Storage which may confuse some people when they run my app.
i tried adding these to my intent: intent.putExtra("android.content.extra.SHOW_ADVANCED", true);
intent.putExtra("android.content.extra.FANCY", true);
intent.putExtra("android.content.extra.SHOW_FILESIZE", true);
which works on some devices, but it's a private API and on some it doesn't work.
So is there a way to like automatically open a specific directory or show a hint dialog with steps explaining which directory they should chose?
private void getSDCardAccess(){
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
Uri uri = Uri.parse(Environment.getExternalStorageDirectory().getPath());
startActivityForResult(intent, REQUEST_EXTERNAL_ACCESS);
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_EXTERNAL_ACCESS && resultCode == RESULT_OK) {
Uri treeUri = null;
if (data != null){
treeUri = data.getData();
}
if (treeUri != null && getActivity() != null) {
getActivity().getContentResolver().takePersistableUriPermission(treeUri,
Intent.FLAG_GRANT_READ_URI_PERMISSION |
Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
StorageUtil storageUtil = new StorageUtil(getActivity());
//Takes the access so that we can use it again after the app reopens
storageUtil.storeTreeUri(treeUri.toString());
Log.d(TAG, "treeUri: " + treeUri.toString());
}else{
Log.d(TAG,"uri is empty!");
}
}
}
is there a way to like automatically open a specific directory
If you have a Uri to it that you got from ACTION_OPEN_DOCUMENT_TREE previously, you should be able to supply that via DocumentsContract.EXTRA_INITIAL_URI. Per the ACTION_OPEN_DOCUMENT_TREE documentation:
Callers can set a document URI through DocumentsContract.EXTRA_INITIAL_URI to indicate the initial location of documents navigator. System will do its best to launch the navigator in the specified document if it's a folder, or the folder that contains the specified document if not.
or show a hint dialog with steps explaining which directory they should chose?
You would need to do that yourself, prior to calling startActivityForResult() for the ACTION_OPEN_DOCUMENT_TREE request.
I am following along with Googles Official docs on how to save a picture take with the camera to the gallery.
They want you to create a file using getExternalFilesDir.
String mCurrentPhotoPath;
private File createImageFile() throws IOException {
// Create an image file name
String imageFileName = "JPEG_" + UUID.randomUUID();
File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File image = File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
storageDir /* directory */
);
// Save a file: path for use with ACTION_VIEW intents
mCurrentPhotoPath = image.getAbsolutePath();
return image;
}
mCurrentPhotoPath is equal to /storage/emulated/0/Android/data/com.mycompany.myapp/files/Pictures/JPEG_22fda6f2-dad9-4dd9-b327-c1130c8df0eb187766077.jpg
But in the very next section, the most important section, Add the Photo to a Gallery,
They say:
If you saved your photo to the directory provided by
getExternalFilesDir(), the media scanner cannot access the files
because they are private to your app.
Which is the exact method getExternalFilesDir() they used. :-(
So I looked at the documentation on that too. And I don't understand well enough yet to figure out which directory method I need to use. I tried getFilesDir() but it doesn't like the Environment.DIRECTORY_PICTURES.
But they do not offer a way to save to the gallery using their method. Their code snippet doesn't work
private void cameraIntent() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getActivity().getPackageManager()) != null) {
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
ex.printStackTrace();
}
if (photoFile != null) {
Uri photoURI = FileProvider.getUriForFile(getActivity().getApplicationContext(), "com.mycompany.myapp.fileprovider", photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(takePictureIntent, REQUEST_CODE_CAPUTURE_IMAGE);
}
}
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_CAPUTURE_IMAGE && resultCode == Activity.RESULT_OK) {
galleryAddPic();
}
}
private void galleryAddPic() {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
File f = new File(mCurrentPhotoPath);
Uri contentUri = Uri.fromFile(f);
mediaScanIntent.setData(contentUri);
this.sendBroadcast(mediaScanIntent);
}
So my app does NOT save a picture to the gallery. I don't see where it saves it at all.
Anyone know what I did wrong?
When you add files to Android’s filesystem these files are not picked up by the MedaScanner automatically, also Android runs a full media scan only on reboot. The problem is that a full scan is taking long time.
One solution is using the static scanFile() method. If you simply need to know when the files have been added, you could use MediaScannerConnection’s static method scanFile() together with a MediaScannerConnection.OnScanCompletedListener. The static method scanFile() is badly named, as it actually takes an array of paths and thus can be used to add multiple files at once and not just one, but it nevertheless does what we want.
Here’s how to use this method:
MediaScannerConnection.scanFile(
getApplicationContext(),
new String[]{file.getAbsolutePath()},
null,
new OnScanCompletedListener() {
#Override
public void onScanCompleted(String path, Uri uri) {
Log.v("grokkingandroid",
"file " + path + " was scanned seccessfully: " + uri);
}
});
the below information is the parameters for the static scanFile() method.
context : The application context
paths : A String array containing the paths of the files you want to add
mimeTypes : A String array containing the mime types of the files
callback : A MediaScannerConnection.OnScanCompletedListener to be notified when the scan is completed
The OnScanCompletedListener itself must implement the onScanCompleted() method. This method gets the filename and the URI for the MediaStore.Files provider passed in as parameters.
I hope this will help.
Sorry for the long question, I have been stuck on this for a month, and I want to provide as much detail as possible...its just a file not found exception in a simple library... :)
I am getting a file not found exception on my variances file:
I do, however, have the variances file:
I am trying to simply implement voice recognition in my background service, so that I can detect when the user says the word hello (using pocketsphinx).
The problem happens in this method: createSphinxDir();
Here is my service:
#Override
public void onCreate() {
super.onCreate();
setupRecog();
}
private void setupRecog() {
String sphinxDir = createSphinxDir();
Log.v(TAG, "ABOUT TO CREATE SETUP");
if (sphinxDir != null) {
try {
Log.v(TAG, "SETTING UP! :)");
mSpeechRecognizer = defaultSetup()
.setAcousticModel(new File(sphinxDir, "en-us-ptm"))
.setDictionary(new File(sphinxDir, "hello.dict"))
.setBoolean("-allphone_ci", true) //WHAT IS THIS
.getRecognizer();
mSpeechRecognizer.addListener(this);
Log.v(TAG, "ADDED LISTENER");
if ((new File(sphinxDir + File.separator + "command.gram")).isFile()) {
mSpeechRecognizer.addKeywordSearch("hello",
new File(sphinxDir + File.separator + "command.gram"));
Log.v(TAG, "ADDED KEYWORD SEARCH! :)");
}
// Or wherever appropriate
mSpeechRecognizer.startListening("wakeup"); //Is this correct?
Log.v(TAG, "STARTED LISTENING");
} catch (IOException e) {
Log.v("ERROR", TAG);
}
}
}
String createSphinxDir() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String sphinxDir = prefs.getString("sphinx", null);
if (sphinxDir == null) {
Assets assets;
Log.v(TAG, "Assets are not synced, should sync now:");
try {
Log.v(TAG, "In try block!");
assets = new Assets(this);
File sphinxDirFile = assets.syncAssets();
Log.v(TAG, "Syncing assets...should set up listener");
if (sphinxDirFile != null) {
sphinxDir = sphinxDirFile.getAbsolutePath();
SharedPreferences.Editor editor = prefs.edit();
editor.putString("sphinx", sphinxDir);
editor.commit();
Log.v(TAG, "Set up listener");
}else{
Log.v(TAG, "sphinxDirFile is null!");
}
} catch (IOException e) { //THIS IS THE PLACE WHERE I AM GETTING THE ERROR!
e.printStackTrace();
Log.d(TAG, e.toString());
}
}
return sphinxDir;
}
I also have all the call back methods (onPartialResult, onResult, etc.) but they never get called.
Earlier I was getting an exception saying the variances .md5 file didn't exist, so I put a space in between the variances and the .md5, but now I am getting this error, and I don't know why...
Please let me know,
Ruchir
Earlier I was getting an exception saying the variances .md5 file didn't exist, so I put a space in between the variances and the .md5, but now I am getting this error, and I don't know why...
You should not do such things, it causes problems, instead you need to follow the documentation:
The standard way to ship resource files with your application in Android is to put them in assets/ directory of your project. But in order to make them available for pocketsphinx files should have physical path, as long as they are within .apk they don't have one. Assets class from pocketsphinx-android provides a method to automatically copy asset files to external storage of the target device. edu.cmu.pocketsphinx.Assets#syncAssets synchronizes resources reading items from assets.lst file located on the top assets/. Before copying it matches MD5 checksums of an asset and a file on external storage with the same name if such exists. It only does actualy copying if there is incomplete information (no file on external storage, no any of two .md5 files) or there is hash mismatch. PocketSphinxAndroidDemo contains ant script that generates assets.lst as well as .md5 files, look for assets.xml.
Please note that if ant build script doesn't run properly in your build process, assets might be out of sync. Make sure that script runs during the build.
To integrate assets sync in your application do the following
Include app/asset.xml build file from the demo application into your application. Edit build.gradle build file to run assets.xml:
ant.importBuild 'assets.xml'
preBuild.dependsOn(list, checksum)
clean.dependsOn(clean_assets)