In an app, I am taking a screenshot of the content of a page, and I want to save it in the Gallery, create a local notification with the image in it, and when the user taps on it, it open the image from the Gallery.
As of now, I am able to take the snapshot and save it using this :
var path = MediaStore.Images.Media.InsertImage(context.ContentResolver, bitmap, "TestImage.png", "Snapshot taken");
I have a few problems with this approach, I can see the image in the Pictures folders from the Gallery, but the name is not the one I specified in this case "TestImage.png". Also, the path returned from this call has the URI scheme "content://...".
After that, I create the local notification with NotificationCompat.Builder with the embedded image, it works well. But when I try to add the PendingIntent to it to open the image I have an issue with the path returned from InsertImage call. The path returned is :
content://media/external/images/media/1292
I tried this :
var path = FileProvider.GetUriForFile(CrossCurrentActivity.Current.AppContext, "com.myapp.fileprovider", new File(path));
but it doesn't work, I get an exception :
Java.Lang.IllegalArgumentException: Failed to find configured root that contains /content:/media/external/images/media/1292
I also tried this :
global::Android.Net.Uri.FromFile(new File(path)), "image/*")
but I receive another exception :
Android.OS.FileUriExposedException: file:///content%3A/media/external/images/media/1292 exposed beyond app through Intent.getData()
This is the code I use to create the notification:
NotificationCompat.BigPictureStyle picStyle = new NotificationCompat.BigPictureStyle();
picStyle.BigPicture(bitmap);
picStyle.SetSummaryText("The snapshot has been saved to your library");
const int pendingIntentId = 0;
PendingIntent pendingIntent =
PendingIntent.GetActivity(CrossCurrentActivity.Current.Activity, pendingIntentId,
new Intent().SetAction(Intent.ActionView)
.SetDataAndType(global::Android.Net.Uri.FromFile(new File(path)), "image/*"), PendingIntentFlags.OneShot);
NotificationCompat.Builder builder = new NotificationCompat.Builder(CrossCurrentActivity.Current.AppContext, "UniqueID")
.SetContentIntent(pendingIntent)
.SetContentTitle("Security center")
.SetContentText("The snapshot has been saved to your library")
.SetSmallIcon(Resource.Drawable.ic_notification);
builder.SetStyle(picStyle);
Notification notification = builder.Build();
NotificationManager notificationManager =
CrossCurrentActivity.Current.Activity.GetSystemService(Context.NotificationService) as NotificationManager;
const int notificationId = 0;
notificationManager.Notify(notificationId, notification);
Do you guys have any idea on how I could save the image with the right name in the Public Gallery folder then access it in the PendingIntent for the local notification ?
Thank you all !
Okay so after a few hours of trial and error, I found out the issue, when parsing the path returned from the MediaStore.Images.Media.InsertImage, I had to use global::Android.Net.Uri.Parse() and not Uri.FromFile(), the latter is only if your path has the URI scheme "file://". By doing that I was able to make the ContentIntent work on the local notification. Only problem now is the name of the file that's not respected.
Related
With new Shared Storage and new Android API 30 requirements I can't find a way to recover a database on new or another device.
Until this time, I used File API to create a copy of the database. Any time user could put this file to any cloud, download from cloud on another device and restore the database on it. Of course, I used WRITE_EXTERNAL_STORAGE.
Now, Google rejects the app and says that we should use MediaStore API which can solve that issues without any permissions. I tried it and it works only on same device. And until if created the database file not copied to/from cloud or moved to/from another folder. In that case cursor.getCount == 0:
String[] projections = new String[]{MediaStore.Downloads._ID};
String selection = MediaStore.Downloads.DISPLAY_NAME + "=?";
String[] selectionArgs = new String[]{"database.db"};
String sortOrder = MediaStore.Downloads.DISPLAY_NAME + " ASC";
Cursor cursor = getApplicationContext().getContentResolver().query(MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL), projections, selection, selectionArgs, sortOrder);
Uri uri = null;
if (cursor != null) {
int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Downloads._ID);
cursor.moveToNext();
long id = cursor.getLong(idColumn);
uri = ContentUris.withAppendedId(MediaStore.Downloads.EXTERNAL_CONTENT_URI, id);
try {
ContentResolver contentResolver = getApplicationContext().getContentResolver();
ParcelFileDescriptor parcelFileDescriptor = contentResolver.openFileDescriptor(uri, "rw");
FileInputStream fileInputStream = new FileInputStream(parcelFileDescriptor.getFileDescriptor());
File dbFile = new File(Environment.getDataDirectory(), "/data/com.example.app/databases/database.db");
FileOutputStream fileOutputStream = new FileOutputStream(dbFile);
byte[] buf = new byte[1024];
int len;
while ((len = fileInputStream.read(buf)) > 0) {
fileOutputStream.write(buf, 0, len);
}
fileOutputStream.close();
fileInputStream.close();
parcelFileDescriptor.close();
restartApp();
} catch (IOException e) {
e.printStackTrace();
}
cursor.close();
}
Something wrong with code or MediaStore API does not provide any actions with created files?
E/DBError: exception
android.database.CursorIndexOutOfBoundsException: Index 0 requested, with a size of 0
Also I tried Google Drive API to copy the database file on cloud directly from app and download it. But it's not working way too, because new device don't know about file ID which used to download files.
Any thoughts on this?
Figured out.
Without any storage permissions your app have access to onw created files with MediaStore API.
But haven't if:
file was moved/copied inside device storage
file was uploaded to cloud and then downloaded from it
app was reinstalled
I tested it on versions 28 (emulation), 29 (device), 30 (emulation).
So in my case anyway I need:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29"/>
Don't know why but for read own file on Android Q not enough only READ_EXTERNAL_STORAGE
I was lucky and Google approved app. Hope you too.
I am using the CachedImage component of ffimageloading. I have a kind of gallery with a carousel view.
All the images are loaded through an internet URL, they are not local images. I would like to add the image sharing function. But I don't want to download the file again, I would like to know if there is a way to access the file that the CachedImage component already downloaded to be able to reuse it in the share function.
try using MD5Helper
var path = ImageService.Instance.Config.MD5Helper.MD5("https://yourfileUrlOrKey")'
Thanks Jason
I share with you how part of my code is:
var key = ImageService.Instance.Config.MD5Helper.MD5("https://yourfileUrlOrKey");
var imagePath = await ImageService.Instance.Config.DiskCache.GetFilePathAsync(key);
var tempFile = Path.Combine(Path.GetTempPath(), "test.jpg");
if (File.Exists(tempFile))
{
File.Delete(tempFile);
}
File.Copy(imagePath, tempFile);
await Share.RequestAsync(new ShareFileRequest
{
Title = "Test",
File = new ShareFile(tempFile)
});
The temporary file I believe, since the cached file has no extension and the applications do not recognize the type.
I want to convert Image to stream from the ImageSource.FromFile.
My Code:
string location = Path.Combine(FileSystem.AppDataDirectory, "data", "Demo.jpg");
image.Source = ImageSource.FromFile(location);
The image is shown properly but I want to convert this image to Stream in Forms.UWP.
I tried the await StorageFile.GetFileFromApplicationUriAsync() this code to get the stream of the image from the location but it throws an exception.
Please suggest me how to convert the image from the file to stream in UWP?
Santhiya A
This path Path.Combine(FileSystem.AppDataDirectory, "data", "Demo.jpg"); is app's local path within UWP platform. And it is not app installation path. if place the Demo.jpg in your project folder, you will not find it with above path. And the parameter of ImageSource.FromFile(string file) is file name, but not the complete path. For the detail please refer this case reply.
I tried the await StorageFile.GetFileFromApplicationUriAsync() this code to get the stream of the image from the location but it throws an exception.
For UWP platform, you could use the following to converter the image file(build property is Content) to steam. For more please refer this document.
private async Task<Stream> GetStreamAsync()
{
var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/Demo.jpg"));
var stream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read);
return stream.AsStream();
}
And if you want use above in Xamarin Forms client project, you need package above method with DependencyService.
I have I have been wasting at least a day trying to make this work. I am trying to play an mp3 file that i placed in Resources/raw once a notification is received. I do not know exactly how to get the Uri. My questions please are:
1.To play a custom file do you have to place it in Resources/raw or can it be also in Assets/Sounds under the Xamarin Android project.
2.How do i get the Uri correctly based on where the mp3 file resides.
This is my code:
private void createNotificationChannel()
{
var channelName = GetString(Resource.String.noti_chan_urgent);
var channelDescription = GetString(Resource.String.noti_chan_urgent_description);
// set the vibration patterm for the channel
long[] vibrationPattern = { 100, 200, 300, 400, 500, 400, 300, 200, 400 };
// Creating an Audio Attribute
var alarmAttributes = new AudioAttributes.Builder().SetUsage(AudioUsageKind.Alarm).Build();
// Create the uri for the alarm file
var alarmUri = Android.Net.Uri.Parse("MyApp.Android/Resources/raw/alarm.mp3"); // this must be wrong because its not working
// create chan1 which is the urgent notifications channel
var chan1 = new NotificationChannel(PRIMARY_CHANNEL_ID, channelName, NotificationImportance.High)
{
Description = channelDescription
};
// set the channel properties
chan1.EnableLights(true);
chan1.LightColor = Color.Red;
chan1.EnableVibration(true);
chan1.SetVibrationPattern(vibrationPattern);
chan1.SetSound(alarmUri, alarmAttributes);
chan1.SetBypassDnd(true);
chan1.LockscreenVisibility = NotificationVisibility.Public;
var manager = (NotificationManager)GetSystemService(NotificationService);
manager.CreateNotificationChannel(chan1);
}
}
I figured it out and I hope this will help someone better than getting a downvote for a question, this is how you do it:
(Note: Make sure you put your mp3 file in your Xamarin Android project under Resources/raw/soundFile.mp3 and build the file as Android Resource).
Then create the Uri like this:
Android.Net.Uri alarmUri = Android.Net.Uri.Parse(${ContentResolver.SchemeAndroidResource}://{Context.PackageName}/{Resource.Raw.soundFile}");
Create the Alarm Attributes like this:
var alarmAttributes = new AudioAttributes.Builder()
.SetContentType(AudioContentType.Sonification)
.SetUsage(AudioUsageKind.Notification).Build();
And finally setSound on the channel itself ONLY from Android Oreo onwards (not on the notification, create the channel at application launch):
chan1.SetSound (alarmUri, alarmAttributes);
uri = Android.Net.Uri.Parse(
"android.resource://" + Application.Context.PackageName + "/raw/sound2");
only change I had to make. to Fredsomofspeech answer.
android 9.
visualstudio 2019 xamarin.forms mobile ios android. sound2.mp3
was running a file android could not play, so make sure download a mp3 file for testing verified to work.
I follow guide enter link description here
I have a issue when I done login my google account it show toast
And browser not auto close to back my.
Thanks!
In you CustomUrlSchemeInterceptorActivity page replace inside OnCreate.
base.OnCreate(savedInstanceState);
global::Android.Net.Uri uri_android = Intent.Data;
Uri uri_netfx = new Uri(uri_android.ToString());
// load redirect_url Page
AuthenticationState.Authenticator.OnPageLoading(uri_netfx);
var intent = new Intent(this, typeof(MainActivity));
intent.SetFlags(ActivityFlags.ClearTop | ActivityFlags.SingleTop);
StartActivity(intent);
this.Finish();
return;
This is the warning message shown in the toast.
We can make it null by adding a piece of code in MainActivity just after initializing Xamarin.Auth.
CustomTabsConfiguration.CustomTabsClosingMessage = null;
And for closing the CustomTab login screen and navigating back to your app, set the LaunchMode of your Custom URL activity to SingleTask.
LaunchMode = LaunchMode.SingleTask
Hope this will help.
Worked for me as well in avoiding the toast:
global::Xamarin.Auth.CustomTabsConfiguration.CustomTabsClosingMessage = null