Storage Access Framework, takePersistableUriPermission - android

In my application user is able to select the downloads directory. If he selects external removable SD card (not an emulated sd card!, but a memory, which is a real physicel microSD card for example), starting from Android 4.4 I am only able to write to it using SAF (Storage Access Framework).
I've figured out how to create and write a single file using SAF:
public void newFile() {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TITLE, "newfile.txt");
startActivityForResult(intent, CREATE_REQUEST_CODE);
}
public void saveFile() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("text/plain");
startActivityForResult(intent, SAVE_REQUEST_CODE);
}
And here is my onActivityResult, where I actually write to file:
public void onActivityResult(int requestCode, int resultCode,
Intent resultData) {
Uri currentUri = null;
if (resultCode == Activity.RESULT_OK) {
if (requestCode == CREATE_REQUEST_CODE) {
if (resultData != null) {
Log.d(TAG, "CREATE_REQUEST_CODE resultData = " + resultData);
}
} else if (requestCode == SAVE_REQUEST_CODE) {
if (resultData != null) {
currentUri = resultData.getData();
getContentResolver().takePersistableUriPermission(currentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
writeFileContent(currentUri);
Log.d(TAG, "SAVE_REQUEST_CODE currentUri = " + currentUri);
}
}
}
}
And also writeFileContent:
private void writeFileContent(Uri uri) {
try {
ParcelFileDescriptor pfd =
this.getContentResolver().
openFileDescriptor(uri, "w");
FileOutputStream fileOutputStream =
new FileOutputStream(pfd.getFileDescriptor());
String textContent = "some text";
fileOutputStream.write(textContent.getBytes());
fileOutputStream.close();
pfd.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
And finally my question:
How do I create other files, and write them after I called getContentResolver().takePersistableUriPermission without a prompt to select a directory in future?
If I'm right, then getContentResolver().takePersistableUriPermission shoudl allow me to do tha

Thanks to #earthw0rmjim answer, and googling, I figgured out a complete solution:
public void saveFile() {
List<UriPermission> permissions = getContentResolver().getPersistedUriPermissions();
if (permissions != null && permissions.size() > 0) {
DocumentFile pickedDir = DocumentFile.fromTreeUri(this, permissions.get(0).getUri());
DocumentFile file = pickedDir.createFile("text/plain", "try2.txt");
writeFileContent(file.getUri());
} else {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("text/plain");
startActivityForResult(intent, SAVE_REQUEST_CODE);
}
}
So firstly, if we don't have PersistedUriPermissions, it will request such permissions. This way here is how onActivityResult looks like
public void onActivityResult(int requestCode, int resultCode,
Intent resultData) {
Uri currentUri = null;
if (resultCode == Activity.RESULT_OK) {
if (requestCode == SELECT_DIR_REQUEST_CODE) {
if (resultData != null) {
Uri treeUri=resultData.getData();
Log.d(TAG, "SELECT_DIR_REQUEST_CODE resultData = " + resultData);
getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri);
DocumentFile file = pickedDir.createFile("text/plain", "try2.txt");
writeFileContent(file.getUri());
}
}
}
}
And the writeFileContent looks same as in the question
private void writeFileContent(Uri uri) {
try {
ParcelFileDescriptor pfd =
this.getContentResolver().
openFileDescriptor(uri, "w");
FileOutputStream fileOutputStream =
new FileOutputStream(pfd.getFileDescriptor());
String textContent = "some text";
fileOutputStream.write(textContent.getBytes());
fileOutputStream.close();
pfd.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

Related

Why Camera activity returning null?

I used below codes for taking image from camera and put it in Image View (imgViewAds).
private void BtnPhoto_Click(object sender, EventArgs e)
{
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.SetVmPolicy(builder.Build());
Intent cameraIntent = new Intent(Android.Provider.MediaStore.ActionImageCapture);
var activities = PackageManager.QueryIntentActivities(cameraIntent, 0);
if (activities.Count > 0)
{
addAds.ImageName = Guid.NewGuid().ToString() + ".jpg";
Java.IO.File imageFile = new Java.IO.File(AdsAdapter.ImagePath(addAds.ImageName));
Android.Net.Uri imageUri = Android.Net.Uri.FromFile(imageFile);
cameraIntent.PutExtra(MediaStore.ExtraSizeLimit, 1024*10);
cameraIntent.PutExtra(MediaStore.ExtraOutput, imageUri);
StartActivityForResult(cameraIntent, 0);
}
else
{
Toast.MakeText(this, "Not Camera", ToastLength.Long).Show();
}
}
And here is OnActivityResult the the camare send result here.
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
if (resultCode == Result.Ok && (data != null))
{
Bundle extras = data.Extras;
Bitmap imageBitmap = (Bitmap)extras.Get("data");
imgViewAds.SetImageBitmap(imageBitmap);
MemoryStream stream = new MemoryStream();
imageBitmap.Compress(Bitmap.CompressFormat.Png, 0, stream);
addAds.AdsImage = stream.ToArray();
}
base.OnActivityResult(requestCode, resultCode, data);
}
but the data that sending to OnActivityResult is null and Image did not come to Image View.
When you pass EXTRA_OUTPUT with a URI to write to, the camera intent will be null and the picture is in the URI that you passed in.
so you could simply remove these two lines:
Android.Net.Uri imageUri = Android.Net.Uri.FromFile(imageFile);
cameraIntent.PutExtra(MediaStore.ExtraOutput, imageUri);
note that in this way you get the thumbnail of the image. so if you want the whole image you could use something like this (I haven't tested the code, but you could get the idea):
if (requestCode == CAMERA_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
addAds.ImageName = Guid.NewGuid().ToString() + ".jpg";
Java.IO.File imageFile = new Java.IO.File(AdsAdapter.ImagePath(addAds.ImageName));
Uri uri = Uri.fromFile(file);
Bitmap bitmap;
try {
bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), uri);
bitmap = cropAndScale(bitmap, 300); // if you mind scaling
profileImageView.setImageBitmap(bitmap);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

SAF - ACTION_CREATE_DOCUMENT - after I save the file on Drive the file is empty

I am a beginner in SAF. What I'm trying to do is super simple to save a config. Let's say the file is .conf.
I copy .conf to conf.txt and I save it on Drive.
Here is my code:
tools.deleteFile(dst); // delete conf.txt if it exists
int res = tools.copyFile(src,dst); // copy .conf to conf.txt
if(res == -1) return;
tools.viewFile(dst);
// verify in Log info that the content of cnf.txt is correct
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TITLE, dst);
startActivity(intent);
I do a save in Drive. The file appears on my pc but when I open it, it's empty.
When I do the inverse: ACTION_OPEN_DOCUMENT
public void onActivityResult(int requestCode, int resultCode,
Intent resultData) {
Uri uri;
if (resultCode == Activity.RESULT_OK){
if (requestCode == 30){
if (resultData != null) {
uri = resultData.getData();
try {
String content =
readFile(uri);
} catch (IOException e) {
e.printStackTrace();
}
The function readFile opens the file and stops while reading because there is no data.
What did I do wrong?
The Intent(Intent.ACTION_CREATE_DOCUMENT) is for CREATING text file, and use onActivityResult() to get the uri (location) from the file, THEN you use OutputStream to WRITE data (byte[]) to the file.
private void createAndSaveFile() {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TITLE, "testFileSam.txt");
startActivityForResult(intent, 1);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1) {
if (resultCode == RESULT_OK) {
try {
Uri uri = data.getData();
OutputStream outputStream = getContentResolver().openOutputStream(uri);
outputStream.write("Hi, welcome to Sam's Android classroom! Have a good day!".getBytes());
outputStream.close();
Toast.makeText(this, "Write file successfully", Toast.LENGTH_SHORT).show();
} catch (IOException e) {
Toast.makeText(this, "Fail to write file", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(this, "File not saved", Toast.LENGTH_SHORT).show();
}
}
}
You are just creating the document but not writing it.
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TITLE, dst);
startActivity(intent);
this will create document and return the uri of the document to your app.
Now you need to write something to this uri that you will get in onActivityResult
#Override
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1) {
if (resultCode == RESULT_OK) {
try {
Uri uri = data.getData();
OutputStream outputStream = getContentResolver().openOutputStream(uri);
outputStream.write("Hi, welcome to Sam's Android classroom! Have a good day!".getBytes());
outputStream.close(); // very important
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

Android get filesize after take camera?

How can i get file size after take camera.
I run the code the following results:filesize is 0.I think that the Camera InputStream is writing.But how can I get the real file size?
private void takeCamera(){
file = File.createTempFile("tp_", ".jpg", dir);
filePathCamera = file.getAbsolutePath();
Uri uri = Uri.fromFile(file);
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
startActivityForResult(intent, REQUEST_CODE_CAMERA);
}
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
switch (requestCode) {
case REQUEST_CODE_CAMERA:
final File file = new File(filePathCamera);
if (!file.exists()) {
result.append("not exists\n");
return;
}
InputStream inputStream = null;
try {
inputStream = new FileInputStream(filePathCamera);
long size = inputStream.available();
result.append("size1:" + size + "\n");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException ioe) {
ioe.printStackTrace();
// ignore
}
}
break;
}
}
}
In onActivityResult in case REQUEST_CODE_CAMERA: after checking if !file.exists() you can check in else part file.length which will give you size of file.

Android CSV file choose from Storage and get its path

I want to import csv from external storage and then update my database but when i am selecting that csv from downloaded folder FileNotFoundExpception comes. Here is the exception System.err: java.io.FileNotFoundException: /document/primary:Download/GuestCSV.csv: open failed: ENOENT (No such file or directory)
Here is my code. Kindly review my code and help me to find a solution.
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("text/*");
startActivityForResult(Intent.createChooser(intent, "Open CSV"), ACTIVITY_CHOOSE_FILE);
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case ACTIVITY_CHOOSE_FILE: {
if (resultCode == RESULT_OK) {
onImport(new File(data.getData().getPath()));
}
}
}
}
public void onImport(File files) {
try {
CSVReader reader = new CSVReader(new FileReader(files));
String[] nextLine;
try {
while ((nextLine = reader.readNext()) != null) {
// nextLine[] is an array of values from the line
String emailID = nextLine[0];
String guestName = nextLine[1];
String guestSource = nextLine[2];
String guestPhone = nextLine[3];
String guestCount = nextLine[4];
String guestCreatedDate = nextLine[5];
String guestModifiedDate = nextLine[6];
GuestDetails guestDetails = new GuestDetails();
guestDetails.setEmail(emailID);
guestDetails.setUsername(guestName);
guestDetails.setPhone(guestPhone);
guestDetails.setSource(guestSource);
guestDetails.setCount(Integer.valueOf(guestCount));
guestDetails.setCreatedDate(guestCreatedDate);
guestDetails.setModifiedDate(guestModifiedDate);
try {
helper.insertGuest(guestDetails);
} catch (SQLiteConstraintException e) {
e.printStackTrace();
}
Toast.makeText(getApplicationContext(), "Data inserted into table...", Toast.LENGTH_SHORT).show();
}
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
i hope this code help you!!
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
Uri uri = Uri.parse(Environment.getExternalStorageDirectory().getPath()+ "/YourFolder/");
intent.setDataAndType(uri, "text/csv");
startActivity(Intent.createChooser(i, "Open folder"));

Android file.canRead() = false when choosing image but not when TAKING photo

I have 2 buttons on an "upload image" page for users to upload an image to a web service. One is for selecting an image that is on your device, the other for taking a photo with your camera.
thisFragment.findViewById(R.id.btnChooseImage).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Assert.assertNotNull("file uri not null before firing intent", mFileUri);
Intent intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
//this is the file that the camera app will write to
intent.putExtra(MediaStore.EXTRA_OUTPUT, mFileUri);
startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE);
}
});
thisFragment.findViewById(R.id.btnTakePhoto).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Assert.assertNotNull("file uri not null before firing intent", mFileUri);
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//this is the file that the camera app will write to
intent.putExtra(MediaStore.EXTRA_OUTPUT, mFileUri);
startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE);
}
});
btnTakePhoto works fine when it loads an ImageView with the result when I TAKE a photo, but when I CHOOSE a photo using the other button the imageView is blank...
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
if(data != null) {
if(data.getData() != null) {
Log.v(LOG_TAG, "intent data: " + data.getData().toString());
}
if(data.getAction() != null) {
Log.v(LOG_TAG, "intent action: " + data.getAction().toString());
}
if(data.getExtras() != null) {
Log.v(LOG_TAG, "intent extras: " + data.getExtras().toString());
}
}
Assert.assertNotNull("file uri in onActivityResult", mFileUri);
Log.v(LOG_TAG, "stored file name is " + mFileUri.toString());
File file = getFileFromUri();
if(file != null) {
Bitmap bm = decodeSampledBitmapFromFile(file, 500, 500);
imgMain.setImageBitmap(bm);
}else{
imgMain.setImageBitmap(null);
}
} else {
parentActivity.finish();
}
}
private File getFileFromUri() {
if(mFileUri != null) {
try {
URI uri;
if(mFileUri.toString().startsWith("file://")){
//normal path
uri = URI.create(mFileUri.toString());
} else {
//support path
uri = URI.create("file://" + mFileUri.toString());
}
File file = new File(uri);
if (file != null) {
//if (file.canRead()) {
return file;
//}
}
} catch (Exception e) {
return null;
}
}
return null;
}
I noticed that when I hit this line of code:
if (file.canRead()) {
return file;
}
file.canRead() is TRUE when I take a picture, but FALSE when I CHOOSE a picture. When I step through and look at the value of the "uri" variable, here they are:
file:///storage/emulated/0/Pictures/IMG_20150916_141518.jpg - this works
file:///storage/emulated/0/Pictures/IMG_20150916_141854.jpg - this doesn't work
any idea what's going on here?
UPDATE: tried using the InputStream approach from ContentResolver, but the bitmap still can't be displayed:
Uri selectedImage = data.getData();
InputStream imageStream = null;
try {
imageStream = parentActivity.getContentResolver().openInputStream(selectedImage);
}catch (FileNotFoundException e){
Log.v(LOG_TAG, "cant load file " + mFileUri.toString());
}
Bitmap bm = BitmapFactory.decodeStream(imageStream);
imgMain.setImageBitmap(bm);

Categories

Resources