File operations in android Q beta - android

1)Target set to Android Q with android.permission.WRITE_EXTERNAL_STORAGE
2) use getExternalStorageDirectoryor getExternalStoragePublicDirectoryand FileOutputStream(file)saving file throws
java.io.FileNotFoundException: /storage/emulated/0/myfolder/mytext.txt open failed: ENOENT (No such file or directory)
3) use getExternalFilesDirapi and saving is success but wont show up even after MediaScannerConnection.scanFile.
/storage/emulated/0/Android/data/my.com.ui/files/Download/myfolder/mytext.txt
What is best way to copy file from internal memory to SDCARD in android Q and refresh.

In Android API 29 and above you can use the below code to store files, images and videos to external storage.
//First, if your picking your file using "android.media.action.IMAGE_CAPTURE"
then store that file in applications private Path(getExternalFilesDir()) as below.
File destination = new File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), fileName);
//Then use content provider to get access to Media-store as below.
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.TITLE, fileName);
values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
//if you want specific mime type, specify your mime type here. otherwise leave it blank, it will take default file mime type
values.put(MediaStore.Images.Media.MIME_TYPE, "MimeType");
values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/" + "path"); // specify
storage path
// insert to media-store using content provider, it will return URI.
Uri uri = cxt.getContentResolver().insert(MediaStore.Files.getContentUri("external"), values);
//Use that URI to open the file.
ParcelFileDescriptor descriptor = context.getContentResolver().openFileDescriptor(uri,"w"); //"w" specify's write mode
FileDescriptor fileDescriptor = descriptor.getFileDescriptor();
// read file from private path.
InputStream dataInputStream = cxt.openFileInput(privatePath_file_path);
//write file into out file-stream.
OutputStream output = new FileOutputStream(fileDescriptor);
byte[] buf = new byte[1024];
int bytesRead;
while ((bytesRead = dataInputStream.read(buf)) > 0)
{
output.write(buf, 0, bytesRead);
}
datanputStream.close();
output.close();
}

In Android Q direct File access is disabled by default for the apps outside their private folders. Here few strategies you can use:
Use the manifest option requestLegacyStorage to have the old behavior but it won't work anymore with Android R, so it's really a short term solution;
Save your files using getExternalFilesDir() method. It's your private folder, other apps could access these files only if they have READ_EXTERNAL_STORAGE permission. In this case it would be good to use FileProvider to grant access to other apps to your files.
Use MediaStore API to insert your media directly. This approach is good for videos, music and photos.
Use the method getPrimaryStorageVolume().createOpenDocumentTreeIntent() of class StorageManager to ask for access to the extenal primary volume. In this case you need user consent and you won't be able to use File api directly anyway, but using DocumentFile class you have a very similar interface, so this is the solution closer to the old behavior. It works if you need to perform operations in foreground and background, i.e. without user interaction with the exception the first interaction to request the permission.
Use ACTION_CREATE_DOCUMENT to create a file using SAF. This option is valid if you do this operation in foreground because you will need the folder selection by the user.
I link Flipper library for point 4, it helps to manage files like in older android versions.

Temporary possibility: you can use android:requestLegacyExternalStorage="true" in <application> tag in Android Manifest.

Add android:requestLegacyExternalStorage="true" in <application> tag in Android Manifest, which will allow you to use All File APIs, for now.

1)Use Document-provider https://developer.android.com/guide/topics/providers/document-provider#create
2)User will be asked to save(total number of files) and get stored in documents folder.
https://gist.github.com/neonankiti/05922cf0a44108a2e2732671ed9ef386

If you use preserveLegacyExternalStorage, the legacy storage model remains in effect only until the user uninstalls your app. If the user installs or reinstalls your app on a device that runs Android 11, then your app cannot opt out the scoped storage model, regardless of the value of preserveLegacyExternalStorage.
Something to remember when using this flag. know more

Related

How to reimplement methods deprecated now that Android 10 is out? For working with MediaStore and storage?

I'm working on refactoring my older app. Decided to migrate to Android Q/10 finally. My app works with sound files so there are 2 methods which are deprecated now. How to implement the same functionality the new way?
Uri uri = MediaStore.Audio.Media.getContentUriForPath(outPath); //outPath is the path where I save my .wav file.
...
String externalRootDir = Environment.getExternalStorageDirectory().getPath();
if (!externalRootDir.endsWith("/")) {
externalRootDir += "/";
}
subDir = "media/audio/music/myfolder/";
...
//getExternalStorageDirectory() is deprecated. Here I'm making a filename for my recording
Also I'm using this constant
values.put(MediaStore.MediaColumns.DATA, outPath); //DATA deprecated
How to reimplement these parts? It is my first time with android Q (and I'm not much experienced in all Android yet anyway). Thanks in advance!
In Android 10 there is a new MediaStore.Audio.Media.RELATIVE_PATH that helps you to define a ‎location to save the file, the problem the allowed directories for audio files are [Alarms, Music, ‎Notifications, Podcasts, Ringtones], you can't just specify any directory you want.‎
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
collection = MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY); // added in #api<=29 to get the primary external storage
// To specify a location instead of the default music directory in external
values.put(MediaStore.Audio.Media.RELATIVE_PATH, (Environment.DIRECTORY_NOTIFICATIONS).getAbsolutePath());
}else{
// Your Old code
}
Uri itemUri = resolver.insert(collection, values);
If you don't add MediaStore.Audio.Media.RELATIVE_PATH the file will be saved at the default ‎directory which is "Music"‎.
I still don't know how to save it to my package folder in Android 10: context.getExternalFilesDir(Environment.DIRECTORY_NOTIFICATIONS)

How to delete a file in /download (ora any other folder) in the android external storage with DocumentProvider through Kotlin?

I'm [still] new on android development and about Java and about Kotlin (also an explanation in Java could be ok, however, I'm studying it also, Kotlin is prefered) and I'm struggling for deleting a simple downloaded file into the ExternalStorage.
Of course I enabled permission for read & write, and, even if this code returns a "True", I still can see the untouched file into my Download folder
here the code:
___UPDATE
// uri of my file in external storage ~/Download dir
var uri = Uri.parse (Environment.getExternalStorageDirectory().getPath() + "/Download/$myFilename$myExtensionVar")
// file object pointing at uri of file in external storage
val downloadedFile = File(uri.toString())
var deletedBool:Boolean = downloadedFile.delete()
println("myTag - deleted Boolean: $deletedBool")
if (deletedBool){
println("myTag - uri of file-to-be-deleted: $uri")
var secondStepToDelete:Int = context.getContentResolver().delete(uri, null, null)
println("myTag - second Step for deletion: $secondStepToDelete")
}
The file i am trying to rid of is a multimedia file (.mp3) and I added the second block of code (the one inside the IF statement) since I found that should work, having to do with the "DocumentProvider" (I'm new and I still don't know how to proper call its methods..) but, of course, It doesn't work at all.
I think I do need the ID (long type i guess) for the file stored into the external storage, however I haven't found yet how to get it
Thanks in advance for the help!
To build a File object, use the File constructor. To build a File object for a location off of a certain root directory, use the two-parameter File(File, String) constructor:
val downloadedFile = File(Environment.getExternalStorageDirectory(), "Download/$myFilename$myExtensionVar")
Unless you are getting a Uri from DownloadManager or something, there is no Uri that you need to delete().
I have more written here on external storage and how to work with it.
In Kotlin
file.deleteRecursively()

Parse error when trying toprogrammatically install APK from android app

To be as succinct as possible:
-APK file is not corrupt.
-I can browse to the APK in the phone's file system and manually install it from there without issue.
-I am using the following code to kick off the install process. File location is confirmed correct:
public void installfromlocal()
{
String downloadfilelocation = getsharedresourcestring("updatepackagelocation");
Log.e("installing from",downloadfilelocation);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(new File(downloadfilelocation)), "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
So far what I could gather from a couple hours on the internet is that apparently I can't make my app install an APK programmatically from external storage. I can also apparently not copy the file to internal storage and install from there.
So what now? Additionally, I get no messages from Logcat. I only get a popup alerting me that there was an error parsing the apk.
I found a solution for me (not so clear why have this issue, but i solve it).
It seems to me that when downloading with DownloadManager you cant access to the downloaded file via URI, and you get access denied (and various file not found exception error) that's why PackageInstaller cannot read at all the manifest (and that's the parse error).
This is what i did, i hope that resolve you problem as well, i know it's not elegant to say the least.
Because of DownloadManager.COLUMN_LOCAL_FILENAME is deprecated i tried with COLUMN_LOCAL_URI to access the file and access its content (q is Cursor)
String strUri = q.getString(q.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
Uri apkUri = Uri.parse(strUri);
with this uri i can access and copy the file to a temp file in getExternalCacheDir()
ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(apkUri, "r");
InputStream inFile = new FileInputStream(pfd.getFileDescriptor());
OutputStream outFile = new FileOutputStream(tmpFile);
//copy
byte[] buffer = new byte[1024];
int length;
while ((length = inFile.read(buffer)) > 0) {
outFile.write(buffer, 0, length);
}
outFile.flush();
inFile.close();
outFile.close();
Grab the file created and get its uri (that is accessible) and start the activity with that uri.
I hope it helps
You should use canonical path of the file. From the docs-
A canonical pathname is both absolute and unique. The precise definition of canonical form is system-dependent. This method first converts this pathname to absolute form if necessary, as if by invoking the getAbsolutePath() method, and then maps it to its unique form in a system-dependent way. This typically involves removing redundant names such as "." and ".." from the pathname, resolving symbolic links (on UNIX platforms), and converting drive letters to a standard case (on Microsoft Windows platforms).

How can I write a public file for my service to pick up on Android?

I have two parts to this question: 1) what is the best solution to my need, and 2) how do I do this?
1) I have a client app which sends bundles to a service app. the bundles can break the limit on bundle size, so I need to write the actual request out and read it in on the service side. Because of this, I can't write to my private internal storage. I've used these pages heavily, and haven't had luck: http://developer.android.com/guide/topics/data/data-storage.html
http://developer.android.com/training/basics/data-storage/files.html
My current understanding is that my best path is to use this to get a public dir:
File innerDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
I then add in my filename:
String fileName = String.valueOf(request.timestamp + "_avoidRoute"+count+++".ggr");
Combing these two results in the full file path:
/storage/emulated/0/Download/GroundGuidance/Route/1425579692169_avoidRoute1.ggr
Which I write to disk like this:
fos = context.openFileOutput(fullPath, Context.MODE_WORLD_WRITEABLE);
fos.write(routeString.getBytes());
fos.close();
When I try to write this to disk I get the error
java.lang.IllegalArgumentException: File /storage/emulated/0/Download/GroundGuidance/Route/1425579692169_avoidRoute1.ggr contains a path separator
Of course it does - I need it to have a path. I've searched online for solutions to this error which tell me to us FileOutputStream to write a full path. I did, but while my app doesn't error and appears to create the file, I'm also not able to view it on my phone in Windows Explorer, leading me to believe that it is creating a file with private permissions. So this brings me to my post and two questions:
1) Is there a different approach I should be trying to take to share large amounts of data between my client and service apps?
2) If not, what am I missing?
Thanks all for reading and trying to help!
Combing these two results in the full file path:
/storage/emulated/0/Download/GroundGuidance/Route/1425579692169_avoidRoute1.ggr
Which I write to disk like this:
fos = context.openFileOutput(fullPath, Context.MODE_WORLD_WRITEABLE);
This is not an appropriate use of Context's openFileOutput() method as that does not take a full path, but rather a filename within an app's private storage area.
If you are going to develop a full path yourself, as you have, then use
fos = new FileOutputStream(fullPath)
The Sharing permission setting is not applicable to the External Storage, though you will need a manifest permission to write (and implicitly read) on your creator, and the one for reading on your consumer.
Or, instead of constructing a full path, you could use your private storage with a filename and Context.MODE_WORLD_WRITEABLE (despite the being deprecated as an advisory) and pass the absolute path of the result to the other app to use with new FileInputStream(path).
Or you could use any of the other data interchange methods - content providers, local sockets, etc.

Android Creating a memory resident input file that can be attached to an email

The final objective will be clear shortly.
I want to create a file object and instead of getting data from a real physical file I want to provide the buffer myself.
Then, I want to use this file, which does not really exist in the sdcard or anywhere outside my app, give it a name and send it by email as an attachment (using the EXTRA_STREAM).
I found the following bit of code, by Adriaan Koster (#adriaankoster), the post Write byte[] to File in Java
// convert byte[] to File
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
File fileFromBytes = (File) ois.readObject();
bis.close();
ois.close();
System.out.println(fileFromBytes);
I used it to create this function
private File fileFromBytes(byte[] buf) {
File f = null;
try {
ByteArrayInputStream bis = new ByteArrayInputStream(buf);
ObjectInputStream ois = new ObjectInputStream(bis);
f = (File) ois.readObject();
bis.close();
ois.close();
}
catch (Exception e) {}
return f;
}
and here is where I am stuck, because when I use it:
// When sent as body the mail is sent OK
// emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, dump());
// When I try to attach the mail is empty
emailIntent.putExtra(android.content.Intent.EXTRA_STREAM, fileFromBytes(dump().getBytes()));
I know from examples I've seen the second argument should be an URI, but: How do I create a virtual URI to fit my file?
EDIT:
The option to attach data directly from within the application is important to certain kind of applications. Namely, security & banking applications that do not want to move sensitive data around too much. Surely if the data does not reach the sdcard and goes directly to a mail attachment it is harder to sniff than within the application memory.
This is not my specific case, but I wanted to point out that this capability is important to have.
The first thing you'll want to do, I imagine, is create a ContentProvider. You can see an example implementation here
https://github.com/dskinner/AndroidWeb/blob/master/src/org/tsg/web/WebContentProvider.java
where in the above link's case, you would add this to your AndroidManifest.xml
<provider
android:name="org.tsg.web.WebContentProvider"
android:authorities="your.package.name" />
Now, you'll have a content uri available for use, content://your.package.name/.
The portion of the above ContentProvider your interested in, again I imagine, is the openFile method. When sharing data by intent across apps, certain things are expected. In your case, you're looking to share some byte data that's meant to be attached to the email.
So if you pass in a content uri to the email app such as content://your.package.name/foo with the appropriate intent flags, then openFile will get called on your ContentProvider. In this case, you can inspect the end of the uri segment to see foo was requested, and return appropriately.
The next issue you bring up is not having the file actually on disk. While I can't vouch for the method you used above (though it looks kosher), what you need to be returning is a ParcelFileDescriptor from your ContentProvider. If you look at the link I provided, you could possibly try to use that as a sample to get the file descriptor from your File object (my knowledge waivers here), but I imagine, the data simply wont be available at that point.
What you do bring up is security though. It's important to note that you can write data to disk privately so only the app has access to the data. I believe, but you might want to double check on this, if that data is private to the app, you can expose it via the ContentProvider and possibly lock down who and how the provider gets used, who can call it, etc. You may want to dig into android docs for that portion or look at some other SO questions.
Anyway, good luck.
Create the file in the application's cache directory. It will be created in the internal filesystem. Use 'getCacheDir()' API for getting the path to the cache dir. Write the data into this dir and then get the URI from the File object using ' Uri.fromFile (File file) '. When you are finished with the file, delete it.
Your application's cache is only available to your app, hence its safe to use for your purpose.
You can do some encryption if the data is too critical.
I think in order to do this, you are going to have to expose a ContentProvider, which will allow you handle a URI. The email application should then openInputStream on your URI, at which point you return an InputStream on your in-memory data.
I've not tried it, but in theory this should work.
i was busy with adding attachment to mail and i can send mail with attachment.
if you want to take a look: can not send mail with attachment in Android

Categories

Resources