Xamarin-System.UnauthorizedAccessException: Access to the path is denied - xamarin

I'm trying to download a file and I'm getting System.UnauthorizedAccessException: Access to the path "/storage/emulated/0/Download/test.pdf" is denied. I have set required permission in Android Manifest file.
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Download Path:
Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads)
If i use the below path as my download path i can able to download the file. But i cant able to share the PDF file to google drive, drop box or any other System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal)
I am using Xamarin.Forms v2.4.0.282 and Xamarin.Android.Support packages v25.4.0.2.
Note: The code was woking fine when use Xamarin.Forms version 2.3.5.256-pre2 and Xamarin.Android.Support packages v23.3.0
Please suggest your ideas to resolve the issue.
Thanks in Advance.

Depending on the version of Android you are using even with the permissions added in the manifest in 6.0 or up the user has to explicitly enable the permission when the app runs and on lower versions permission is asked during install. For example, on startup of the app I created a method to check if it is enabled and request permission if it's not.
private void CheckAppPermissions()
{
if ((int)Build.VERSION.SdkInt < 23)
{
return;
}
else
{
if (PackageManager.CheckPermission(Manifest.Permission.ReadExternalStorage, PackageName) != Permission.Granted
&& PackageManager.CheckPermission(Manifest.Permission.WriteExternalStorage, PackageName) != Permission.Granted)
{
var permissions = new string[] { Manifest.Permission.ReadExternalStorage, Manifest.Permission.WriteExternalStorage };
RequestPermissions(permissions, 1);
}
}
}
You can also use the support library to do this, which is simpler and allows you to not have to check the Android version. For more info check out google's documentation.

If targeting API 29+, you will get the error even if you request the permission and user grants it, because they changed how storage works.
The correct solution is to look how it should be done on API 29+ and do it.
But if you are like me, tired of Android making things more complicated every day, just add android:requestLegacyExternalStorage="true" to your manifest <application> tag and you are saved until you start targeting API 30.

Those of you who are facing this issue after your app is Targeting API29 or higher, please go to this link and check LandLu's Answer:
https://forums.xamarin.com/discussion/171039/saving-files-to-external-storage
Earlier I was accessing Folder path using
return Android.OS.Environment.ExternalStorageDirectory.AbsolutePath;
But using this line solved my problem:
return Android.App.Application.Context.GetExternalFilesDir("").AbsolutePath;

You need user's permission on run time even you have mentioned them in your manifest file if you are running Android api level 23 or greater. Check and if user has not yet granted granted READ_EXTERNAL_STORAGE & WRITE_EXTERNAL_STORAGE, use the bellow code;
var permissions = new string[] { Manifest.Permission.ReadExternalStorage, Manifest.Permission.WriteExternalStorage };
RequestPermissions(permissions, 77);
If i use the below path as my download path i can able to download the
file. But i cant able to share the PDF file to google drive, drop box
or any other
System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal)
You are storing files on app's private storage. All files saved to the internal storage are private to your application and other applications ( google drive, drop box or any other ) cannot access them (nor can the user). You can use any public folder for that purpose;
var finalPath = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads);

replace
Android.OS.Environment.ExternalStorageDirectory.AbsolutePath;
with
Android.App.Application.Context.GetExternalFilesDir("").AbsolutePath;

Related

Xamarin - Android 13 Notification Permission Prompt

As we know, Android 13 is introducing runtime notification permission. Here's the Android developer documentation on this.
We already have a priming page where we show the notification permission prompt in iOS. We just need to do this for all users on Android 13.
After reading the documentation, i've added the following:
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
However I can't seem to find a way to prompt Android users. Has anyone had any success with this/can point me in the right direction?
Also, we've noticed all of our users who have upgraded to Android 13 have notifications turned off by default. Is there a way we can add a link to the notifications permission in the settings?
I am currently using this to achieve that, in your MainActivity put this
protected override void OnStart()
{
base.OnStart();
const int requestLocationId = 0;
string[] notiPermission =
{
Manifest.Permission.PostNotifications
};
if ((int)Build.VERSION.SdkInt < 33) return;
if (this.CheckSelfPermission(Manifest.Permission.PostNotifications) != Permission.Granted)
{
this.RequestPermissions(notiPermission, requestLocationId);
}
}
From document Notification runtime permission, we know that:
To request the new notification permission from your app, update your
app to target Android 13 and complete a similar process compared to
requesting other runtime permissions.
And from document POST_NOTIFICATIONS, we also find that :
POST_NOTIFICATIONS Added in API level 33
public static final String POST_NOTIFICATIONS Allows an app to post
notifications
Protection level: dangerous
Constant Value: "android.permission.POST_NOTIFICATIONS"
That is the Protection level of POST_NOTIFICATIONS is dangerous, so we need to
add Requesting Runtime Permissions.
For more information, you can check : Requesting Runtime Permissions in Android Marshmallow.
You can also check Permissions In Xamarin.Android here.
And there is a sample here:https://github.com/xamarin/monodroid-samples/tree/main/android-m/RuntimePermissions.

Runtime request permission in NativeScript Android app

I code an application in Native Script and I need to use requestPermission after first launch app. I know how to use request permission, but I don't know how to make it work after first running the application. Where I must use request-permission function in app ? In ngOnInit () ?
You may use nativescript-permissions plugin to acquire runtime permissions on Android.
Use hasPermission(permissionName); method to know whether your app already has the permission Or you are yet to acquire it.
Generally it's recommended to ask for permission only when it's absolute necessary. For example, if you want to access micro phone to record anything you would request for permission only when user tries to record one, not upon launch.
You could still ask permissions upon launch, that would work. But in my opinion that could be annoying to the user. May be he is not intended to use that particular feature of the app but just the rest.
// HTML
<Button text="Take Permissions" (tap)="getPermission()"></Button>
// TS File
import * as camera from "nativescript-camera";
getPermission() {
camera.requestPermissions().then(
function success() {
console.log('granted');
},
function failure() {
console.log('failure');
}
);
}
// Search AndroidManifest.xml and add this code in all the occurrences.
<uses-permission android:name="android.permission.CAMERA" />
That's all!

Xamarin Android share PDF. Permission denied for the attachment [duplicate]

My app creates mails with attachments, and uses an intent with Intent.ACTION_SEND to launch a mail app.
It works with all the mail apps I tested with, except for the new Gmail 5.0 (it works with Gmail 4.9), where the mail opens without attachment, showing the error: "Permission denied for the attachment".
There are no useful messages from Gmail on logcat. I only tested Gmail 5.0 on Android KitKat, but on multiple devices.
I create the file for the attachment like this:
String fileName = "file-name_something_like_this";
FileOutputStream output = context.openFileOutput(
fileName, Context.MODE_WORLD_READABLE);
// Write data to output...
output.close();
File fileToSend = new File(context.getFilesDir(), fileName);
I'm aware of the security concerns with MODE_WORLD_READABLE.
I send the intent like this:
public static void compose(
Context context,
String address,
String subject,
String body,
File attachment) {
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.setType("message/rfc822");
emailIntent.putExtra(
Intent.EXTRA_EMAIL, new String[] { address });
emailIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
emailIntent.putExtra(Intent.EXTRA_TEXT, body);
emailIntent.putExtra(
Intent.EXTRA_STREAM,
Uri.fromFile(attachment));
Intent chooser = Intent.createChooser(
emailIntent,
context.getString(R.string.send_mail_chooser));
context.startActivity(chooser);
}
Is there anything I do wrong when creating the file or sending the intent? Is there a better way to start a mail app with attachment? Alternatively - has someone encountered this problem and found a workaround for it?
Thanks!
I was able to pass a screenshot .jpeg file from my app to GMail 5.0 through an Intent. The key was in this answer.
Everything I have from #natasky 's code is nearly identical but instead, I have the file's directory as
context.getExternalCacheDir();
Which "represents the external storage directory where you should save cache files" (documentation)
GMail 5.0 added some security checks to attachments it receives from an Intent. These are unrelated to unix permissions, so the fact that the file is readable doesn't matter.
When the attachment Uri is a file://, it'll only accept files from external storage, the private directory of gmail itself, or world-readable files from the private data directory of the calling app.
The problem with this security check is that it relies on gmail being able to find the caller app, which is only reliable when the caller has asked for result. In your code above, you do not ask for result and therefore gmail does not know who the caller is, and rejects your file.
Since it worked for you in 4.9 but not in 5.0, you know it's not a unix permission problem, so the reason must be the new checks.
TL;DR answer:
replace startActivity with startActivityForResult.
Or better yet, use a content provider.
Use getExternalCacheDir() with File.createTempFile.
Use the following to create a temporary file in the external cache directory:
File tempFile = File.createTempFile("fileName", ".txt", context.getExternalCacheDir());
Then copy your original file's content to tempFile,
FileWriter fw = new FileWriter(tempFile);
FileReader fr = new FileReader(Data.ERR_BAK_FILE);
int c = fr.read();
while (c != -1) {
fw.write(c);
c = fr.read();
}
fr.close();
fw.flush();
fw.close();
now put your file to intent,
emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(tempFile));
You should implement a FileProvider, which can create Uris for your app's internal files. Other apps are granted permission to read these Uris. Then, simply instead of calling Uri.fromFile(attachment), you instantiate your FileProvider and use:
fileProvider.getUriForFile(attachment);
Google have an answer for that issue:
Store the data in your own ContentProvider, making sure that other apps have the correct permission to access your provider. The preferred mechanism for providing access is to use per-URI permissions which are temporary and only grant access to the receiving application. An easy way to create a ContentProvider like this is to use the FileProvider helper class.
Use the system MediaStore. The MediaStore is primarily aimed at video, audio and image MIME types, however beginning with Android 3.0 (API level 11) it can also store non-media types (see MediaStore.Files for more info). Files can be inserted into the MediaStore using scanFile() after which a content:// style Uri suitable for sharing is passed to the provided onScanCompleted() callback. Note that once added to the system MediaStore the content is accessible to any app on the device.
Also you can try set permissions for your file:
emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
And finally you can copy/store your files in external storage - permissions not needed there.
I tested it and I found out that it was definitely private storage access problem.
When you attach some file to Gmail (over 5.0) do not use the file from private storage such as /data/data/package/. Try to use /storage/sdcard.
You can successfully attach your file.
Not sure why GMail 5.0 doesn't like certain file paths (which I've confirmed it does have read access to), but an apparently better solution is to implement your own ContentProvider class to serve the file. It's actually somewhat simple, and I found a decent example here: http://stephendnicholas.com/archives/974
Be sure to add the tag to your app manifest, and include a "android:grantUriPermissions="true"" within that. You'll also want to implement getType() and return the appropriate MIME type for the file URI, otherwise some apps wont work with this... There's an example of that in the comment section on the link.
I was having this problem and finally found an easy way to send email with attachment. Here is the code
public void SendEmail(){
try {
//saving image
String randomNameOfPic = Calendar.DAY_OF_YEAR+DateFormat.getTimeInstance().toString();
File file = new File(ActivityRecharge.this.getCacheDir(), "slip"+ randomNameOfPic+ ".jpg");
FileOutputStream fOut = new FileOutputStream(file);
myPic.compress(Bitmap.CompressFormat.JPEG, 100, fOut);
fOut.flush();
fOut.close();
file.setReadable(true, false);
//sending email
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_EMAIL, new String[]{"zohabali5#gmail.com"});
intent.putExtra(Intent.EXTRA_SUBJECT, "Recharge Account");
intent.putExtra(Intent.EXTRA_TEXT, "body text");
//Uri uri = Uri.parse("file://" + fileAbsolutePath);
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(Intent.createChooser(intent, "Send email..."),12);
}catch (Exception e){
Toast.makeText(ActivityRecharge.this,"Unable to open Email intent",Toast.LENGTH_LONG).show();
}
}
In this code "myPic" is bitmap which was returned by camera intent
Step 1: Add authority in your attached URI
Uri uri = FileProvider.getUriForFile(context, ""com.yourpackage", file);
Same as your manifest file provide name
android:authorities="com.yourpackage"
Step 2`; Add flag for allow to read
myIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

Programmatically download APK from Google Drive doesn't work

When finding random apk's online through urls, I can successfully download and install them, the user is prompted to ask if they want to install comes up. But when I upload the same apk's to Google drive, and then run the download url from Google drive, the apk's doesn't work. I get a "There was a problem while parsing the package" on the device screen. I put a log to see how much data is being downloaded. And it appears that the apk's being downloaded from google drive are barely the size of what the apk's should be. Around 50k instead of 4MB. I see a lot of questions online about this, but none have talked about Google play not sending the full file. Is there something I'm missing in order to get the full apk downloaded from Google drive? here is the code,
private void downloadApk(){
// checkVersion();
String extStorageDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString();
File folder = new File(extStorageDirectory);
folder.mkdirs();
File file = new File(folder, "app-debug.apk");
try {
file.createNewFile();
} catch (IOException e1) {
e1.printStackTrace();
}
DownloadApkTask downloadApkTask = new DownloadApkTask(APKURL,file);
downloadApkTask.execute();
}
public class DownloadApkTask extends AsyncTask<String, Void, Void> {
String fileURL;
File directory;
public DownloadApkTask(String fileURL,File directory) {
this.fileURL = fileURL;
this.directory = directory;
}
#Override
protected Void doInBackground(String... params) {
Log.v("DO in Back started","Started");
try {
FileOutputStream f = new FileOutputStream(directory);
URL u = new URL(fileURL);
HttpURLConnection c = (HttpURLConnection) u.openConnection();
c.setRequestMethod("GET");
c.connect();
InputStream in = c.getInputStream();
byte[] buffer = new byte[1024];
int len1 = 0;
while ((len1 = in.read(buffer)) > 0) {
Log.v("PROGREsS", String.valueOf(len1));
f.write(buffer, 0, len1);
}
f.close();
directory.setReadable(true,false);
} catch (Exception e) {
System.out.println("exception in DownloadFile: --------"+e.toString());
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void stringReturn) {
super.onPostExecute(stringReturn);
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)+"/app-debug.apk");
Log.v("STARTING INSTALLATION","-----");
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
startActivity(intent);
}
}
Based from this page, parsing error occurs on app installment.
When you try to install an application suddenly a window pop-ups saying "there is a problem parsing the package" which means the application cannot be installed due to apk parser i.e. parsing issue.
There are several reasons why this parsing error occurs & definitely one of them is responsible for your parsing error:
File may be downloaded incompletely.
Application might be not suitable for your hardware or OS version.
Due to security issue settings
Corrupted APK file.
Follow the steps shown below for fixing the android parse error on your mobile devices:
Check Manifested app apk file.
Change the Andriomanifest.xml file to its default setting & also check the name of that file. If the original name of the file is “aap.apk” & if you renamed it as "app1.apk" then also it might cause an error. If you have some knowledge of coding, look into the app code if there is some problem with coding.
Security settings.
For the security purpose, the phone has an inbuilt setting that doesn't allow installing applications from a 3rd party provider other than mobile apps provided by play store. Don’t install an app from the non-trusted website. That might really risk your mobile.
Enable USB debugging.
Go to the settings >> Scroll down then, at last, you will see option “About device” select it.
Look for option “build number.”
Tap on “Build number” for 7 times.
You will see a message “you are now a developer.”
Once you enable to go back to settings
Choose “Developer options.”
Tick mark "USB debugging."
Corrupted App file.
The parse error may cause due to corrupted file too. In this case, download a new but complete APK file, & try again to install it again. This might help you.
Disable Antivirus.
If you have installed applications like antivirus & cleaner apps, then this can also prevent some apps installation. This prevention is due to the safety purpose of the handset. They block suspicious downloads from non-trusted sites. If you really want to install that app then disable the antivirus temporarily.
Clear cache cookies of play store.
Open google play store
Select sidebar & choose option “settings.”
In general settings, you will find out to “clear local search history.”

Where to put settings in Windows 7 on Okuma control

I'm writing an application that will run on the Okuma control and have application settings.
Since one of the conditions is that application's settings must be easily backed up, I'm keeping them in the application directory. It works on the control because applications go to the D: but if someone installs the application on a PC on the C drive, the application can't access it's own application directory and it gets errors.
Conditions:
Windows 7
P300 control
Application being installed to D-drive
Has to work if someone installs to the C-drive on a PC
Is there a standard spot to put all application settings?
Continue to keep your application settings and other data in the application's install directory. There is no need to change the directory locations just for a "PC only" install.
The solution to file access issues is to change file permissions during install.
For example, this answer someone posted using WIX installer.
A similar question is answered here.
You could use code similar to this to change permissions during install (when the user has admin privileges)
using System.Security.Principal;
public static void SetPermissions()
{
String path = GetPath();
try
{
// Create security idenifier for all users (WorldSid)
SecurityIdentifier sid = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
DirectoryInfo di = new DirectoryInfo(path);
DirectorySecurity ds = di.GetAccessControl();
// add a new file access rule w/ write/modify for all users to the directory security object
ds.AddAccessRule(new FileSystemAccessRule(sid,
FileSystemRights.Write | FileSystemRights.Modify,
InheritanceFlags.ObjectInherit | InheritanceFlags.ContainerInherit, // all sub-dirs to inherit
PropagationFlags.None,
AccessControlType.Allow)); // Turn write and modify on
// Apply the directory security to the directory
di.SetAccessControl(ds);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}

Resources