I'm developing an app that saves data into a database, I'm trying to backup and restore that database which I am able to do, my issue is with the "ominous" permmission popup on API30+
Allow management of all files
Allow this app to access modify and delete files on your device.....
Allow this app to access, modify and delete files on the device or any connected storage devices? this app may access files without asking you.
I'm not trying to do any of these things, I just want permission to do the backup/restore thing
here's my code for requesting permission:
private void requestStoragePermissionExport(){
if( (Build.VERSION.SDK_INT >= 30 )){
try {
Intent intent = new Intent(Manifest.permission.MANAGE_EXTERNAL_STORAGE);
intent.addCategory("android.intent.category.DEFAULT");
intent.setData(Uri.parse(String.format("package:%s",getApplicationContext().getPackageName())));
startActivityForResult(intent, 2296);
} catch (Exception e) {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
startActivityForResult(intent, 2296);
}
}else{
ActivityCompat.requestPermissions(this, new String[]{
Manifest.permission.WRITE_EXTERNAL_STORAGE}, BACKUP_CODE);
}
}
is there a better way to handle this?
Google is restricting use of broad file permissions such as MANAGE_EXTERNAL_STORAGE. You can use Storage Access Framework to gain limited access to certain files or directories.
// Request code for selecting a PDF document.
const val PICK_PDF_FILE = 2
fun openFile(pickerInitialUri: Uri) {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/pdf"
// Optionally, specify a URI for the file that should appear in the
// system file picker when it loads.
putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
}
startActivityForResult(intent, PICK_PDF_FILE)
}
Or if you want to access an entire directory;
fun openDirectory(pickerInitialUri: Uri) {
// Choose a directory using the system's file picker.
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
// Optionally, specify a URI for the directory that should be opened in
// the system file picker when it loads.
putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
}
startActivityForResult(intent, your-request-code)
}
There are some restrictions to which paths you can access. You can read more about it here
https://developer.android.com/training/data-storage/shared/documents-files
You can just backup your db file to the public Documents directory.
No need for the permissions you mentioned.
Alright so, after a bit of research I found the best solution for myself is as follows:
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + File.separator + "foldername"
this doesn't require permissions and works on below and above API 30
Related
We are storing an update of our APK in internal storage of the device. Following is the path of the location:
/storage/emulated/0/Android/data/package_name/files/APK/app_name.apk
We are able to store the updated apk at the above path. But we are unable to install the APK programmatically using java(Because we don’t want to add REQUEST_INSTALL_PACKAGES permission). We decided to atleast open this path so that user can manually install the apk.
SDK level is 32
We have tried multiple ways to open this path, but only Downloads folder is getting opened.
Uri selectedUri = Uri.parse("/storage/emulated/0/Android/data/package_name/files/APK");
intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(selectedUri, "*/*");
intent.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivity(intent);
Please suggest a proper way to open the directory.
For similar purpose I'm using below snippet, but be aware thats old app and is still targeting 27, so before e.g. Scoped Storage. Still, afaik, code works well in newer OS versions
public static void sendInstallUpdateIntent(Context ctx, File pendingUpdate) {
Intent intent;
if (Build.VERSION.SDK_INT >= 24) {
Uri uri = FileProvider.getUriForFile(
ctx, ctx.getApplicationContext().getPackageName() + ".fileprovider", pendingUpdate);
intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
intent.setData(uri);
} else {
Uri uri = Uri.fromFile(pendingUpdate);
intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(uri, "application/vnd.android.package-archive");
}
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
ctx.startActivity(intent);
}
note you have to implement FileProvider, check out official DOC and some basic configuration on SO
I am trying to open a directory with the help of an intent to show the user what is the content inside that folder but I am unable to do so,
but I don't know why the folder won't open and I get this "Can't use this folder" on the file manager.
open.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + "/" + "XYZ";
Uri uri = Uri.parse(path);
Toast.makeText(MainActivity.this, ""+path, Toast.LENGTH_SHORT).show();
openDirectory(uri);
}
});
The method
public void openDirectory(Uri uriToLoad){
// Choose a directory using the system's file picker.
int result = 1;
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
// Optionally, specify a URI for the directory that should be opened in
// the system file picker when it loads.
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uriToLoad);
startActivityForResult(intent, result);
}
I want to open the folder XYZ after clicking on the button
I am trying to open a directory with the help of an intent to show the user what is the content inside that folder
There is no standard Intent action for that, sorry. Your code is trying to let the user select a document tree.
It is also doing that incorrectly, as EXTRA_INITIAL_URI does not take a file:// Uri as a value. That needs to be some Uri that you obtained previously from the Storage Access Framework, such as via some past ACTION_OPEN_DOCUMENT_TREE request.
I don't know why the folder won't open and I get this "Can't use this folder" on the file manager
From Android's standpoint, your EXTRA_INITIAL_URI value is little better than a random string.
But the user won't know where actually XYZ folder is
Then perhaps you should have let the user choose the location in the first place, rather than forcing a particular location. For example, you could use ACTION_CREATE_DOCUMENT to let the user decide where to place the document on the user's device (or the user's cloud storage, the user's network file server, etc.).
As far as I know using Intent you can browse, open and create files, that shared for all apps by the system. In this case for get file path to open, you can use next code like this (pardon for my Kotlin):
private var launcherForResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
result.data.also { uri -> filePath = uri.toString() }
}
}
private fun getFilePath() {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
type = "*/*"
addCategory(Intent.CATEGORY_OPENABLE)
}
launcherForResult.launch(intent)
}
As for the directory, it usually opens the last one that was opened before.
If you don't want other applications to see your files, store them in your application's private directory
I'm attempting to create a blank file in an Android app. My code is working fine on Android 5, but when I tested with Android 7 or higher it fails without printing an exception.
My code is:
downloadLocation = new File(Environment.getExternalStorageDirectory() + "/" + "folder" + System.currentTimeMillis());
downloadLocation.mkdir();
downloadFile = new File(downloadLocation, "file" + System.currentTimeMillis());
downloadFile.createNewFile();
Note, I have declared the variables and everything, I just didn't include that. Both the downloadLocation and downloadFile variables are File type.
On the Android 5 physical device that I was using, it worked just fine. I then tested with an Android 7 virtual device, but it fails without printing an error in the log. The issue is with the second block of code, specifically the last line. I know this because I placed a log between each line and every log outputs except for the log after the file is created.
It's very hard to debug something when there is no error displayed, dispite there being a catch error that is suppose to print the stack trace and the exception.
Any ideas?
Just as a note, I know that the file and folder don't already exist because I've appended a time stamp to the name to prevent that for testing purposes, since I know 100% that the file will have a unique name in release. That's why I haven't encased each block in an if statement that would check to make sure that the file/folder doesn't already exist.
Your code is probably throwing a file not found exception. After downloadLocation.mkdir() call:
if (downloadLocation.exists()) { System.out.println("File Exists"); }
You'll notice the message won't print. This is because you never had permissions to write to the directory.
Add the following to your AndroidManifest.xml:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
An easy way to handle requesting permissions is a great library: EasyPermissions
Using EasyPermissions, your code might look like:
1.) You call a function in your activity to trigger creating the file:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
writeFiles();
}
2.) writeFiles will need to ensure permissions are granted before executing the file operations:
#AfterPermissionGranted(PERMISSIONS_REQUEST_CODE)
private void writeFiles() {
String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
if (EasyPermissions.hasPermissions(this, permissions)) {
try {
File downloadLocation = new File(Environment.getExternalStorageDirectory() + "/" + "folder" + System.currentTimeMillis());
downloadLocation.mkdir();
if (downloadLocation.exists()) {
Log.i("writeFiles()", downloadLocation.getAbsolutePath());
}
File downloadFile = new File(downloadLocation, "file" + System.currentTimeMillis());
downloadFile.createNewFile();
} catch (Exception e) {
Log.e("ERROR", e.getMessage(), e);
}
} else {
// Do not have permissions, request them now
EasyPermissions.requestPermissions(this, "Your permission rationale", PERMISSIONS_REQUEST_CODE, permissions);
}
}
3.) In order for EasyPermissions to automatically call writeFiles() again after permissions are granted, you'll need to add this override:
#Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
// Forward results to EasyPermissions
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
}
You'll notice PERMISSIONS_REQUEST_CODE used a couple of times. This is just a constant integer you use to identify the permissions requests when onRequestPermissionsResult is called from the Activity. This can really be any number you want (such as 1000) so long as it's unique. I linked to the documentation of EasyPermissions which will further explain how it works, but this greatly simplifies how you handle runtime permissions.
Edit:
The following links will help explain more about requesting permissions for your apps:
https://developer.android.com/training/permissions/requesting
https://developer.android.com/guide/topics/permissions/overview#normal-dangerous
Android Marshmallow 6.0: Asking For Permission
In the past, when users would install your app in the Play Store they would be presented with a list of the permissions your app wants. If the user proceeded to install the app, all permissions were automatically granted. Android 6 changed this and now users are given more control over which permissions they want to grant each app.
Some permissions still don't need user approval, these are qualified as normal permissions. An example would be the INTERNET permission. Other permissions that could affect privacy, system data, etc... are considered dangerous permissions and will require the user to grant them via the permissions dialog as seen above. The second URL I posted above will show you some tables of what each permission is qualified as.
Keep in mind if users deny a permission and check the "Don't show again" box then the dialog will never be shown to them again and they will have to go to the App in the system Settings and grant the permission from there. If you ever want to help the user get to this screen you can use the following intent:
Intent intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package", getPackageName(), null));
startActivity(intent);
I'm trying to pick a file via an Intent.
What I tryed till now is this:
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
startActivityForResult(intent, FILE_SELECT_CODE);
But with this, I can only pic Photos with the galery app.
My goal is to pic ANY file via the standard file manager of Android/Samsung.
This didn't work either:
String manufactures = android.os.Build.MANUFACTURER;
if(manufactures.equalsIgnoreCase("samsung"))
{
Intent intent = new Intent("com.sec.android.app.myfiles.PICK_DATA");
intent.putExtra("CONTENT_TYPE", "*/*");
startActivityForResult(intent, FILE_SELECT_CODE);
}
Thx for your help!
My goal is to pic ANY file via the standard file manager of Android/Samsung.
Android does not have a "standard file manager".
If your minSdkVersion is 19 or higher, you are welcome to use the Storage Access Framework (e.g., ACTION_OPEN_DOCUMENT), which is the closest thing that Android now has to a "standard file manager".
Otherwise, you are limited to whatever ACTION_GET_CONTENT-supporting apps that the user has installed, or creating your own file-selection UI, or using one of many existing libraries for selecting files.
Hi I want to browse to a file explorer and select a pdf or image present in some directory.
I want the code to do the same.
the below code takes me to gallery and help me choose image but I want to move to file explorer then select file and accordingly I want the code in onactivityResult after selecting.
browsePic.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Intent i = new Intent(
Intent.ACTION_PICK,
android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI
);
startActivityForResult(i, LOAD_IMAGE_RESULTS);
}
});
I believe you can throw out an open intent for a file chooser using the following.
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
try{
startActivityForResult(intent, LOAD_IMAGE_RESULTS);
} catch (ActivityNotFoundException e){
Toast.makeText(YourActivity.this, "There are no file explorer clients installed.", Toast.LENGTH_SHORT).show();
}
The trouble is however, this assumes your user has a file browser open to accepting intents installed on their device, when often no such apps are installed on a device by default.
As in the code above, you may have to throw up a dialog if no Activities exist that can accept this intent, explaining that they need to install a file browser. You could even recommend one that you know works with your application.
I hope this helps.
i think you could do something like this
File strDir = new File("/mnt/"); // where your folder you want to browse inside android
if( strDir.isDirectory()){
//do something
}