Reputation: 585
This used to work, record a sound in order to use for the custom sound for a push notification channel. Now it does not anymore. What changed please, why would this not work anymore, there is no sound when notification comes in?
In order to use a recorded sound for push notifications, I used to copy it to external storage from where it can be accessed at runtime like this:
the path for an external path was like this:
/storage/emulated/0/customNotificationSoundRecording.wav
Here is the full code, first record the sound:
private async Task RecordAudio()
{
buttonSave.IsEnabled = false;
try
{
if (!recorder.IsRecording)
{
buttonRecord.IsEnabled = false;
buttonPlay.IsEnabled = false;
// only if ios
if (Device.RuntimePlatform == Device.iOS)
DependencyService.Get<IAudioService>().PrepareRecording();
Increment = 1 / (double)AppConstants.MAX_RECORD_TIME;
Set_Timer();
var recordTask = await recorder.StartRecording();
buttonRecord.Text = "Stop";
buttonRecord.IsEnabled = true;
// get the recorded file
var recordedAudioFile = await recordTask;
// isRecording = false;
if (recordedAudioFile != null)
{
// first save the file from cache to AppData
var recordingFileDestinationPath = Path.Combine(FileSystem.AppDataDirectory, AppConstants.CUSTOM_ALERT_FILENAME);
//if (File.Exists(recordingFileDestinationPath))
if (CustomAlertSoundExists(recordingFileDestinationPath))
{
File.Delete(recordingFileDestinationPath);
}
File.Copy(recordedAudioFile, recordingFileDestinationPath);
Preferences.Set(AppConstants.CUSTOM_RECORDING_EXISTS_KEY, true);
if (Device.RuntimePlatform == Device.iOS)
{
DependencyService.Get<IGroupUserPrefs>().SetBoolValueForKey("isCustomAlert", true);
}
buttonSave.IsEnabled = true;
}
if (secondsElapsed >= AppConstants.MAX_RECORD_TIME)
{
Preferences.Set(AppConstants.RECORDING_LENGTH_SECONDS_KEY, secondsElapsed);
secondsElapsed = 0;
Reset_Timer();
}
buttonRecord.Text = "Record";
buttonPlay.IsEnabled = true;
}
else // stop button clicked
{
buttonRecord.IsEnabled = false;
await recorder.StopRecording();
buttonSave.IsEnabled = true;
// isRecording = false;
Reset_Timer();
// save last recording length in seconds
Preferences.Set(AppConstants.RECORDING_LENGTH_SECONDS_KEY, secondsElapsed);
//reset seconds elapsed
secondsElapsed = 0;
buttonRecord.IsEnabled = true;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
And then copy the recording to external storage, used to be that its the only location from where it can be used for push notification sound:
public async void CopyRecordingToExternalStorage(string filePath)
{
var recordingFileExternalPath = Path.Combine(Android.OS.Environment.ExternalStorageDirectory.Path, AppConstants.CUSTOM_ALERT_FILENAME);
// made some other attempts with different paths.
// var recordingFileExternalPath = Path.Combine(FileSystem.AppDataDirectory, AppConstants.CUSTOM_ALERT_FILENAME);
//var recordingFileExternalPath = Path.Combine(Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryAlarms).AbsolutePath, AppConstants.CUSTOM_ALERT_FILENAME);
var storageStatus = await CrossPermissions.Current.CheckPermissionStatusAsync(Plugin.Permissions.Abstractions.Permission.Storage);
if (storageStatus != Plugin.Permissions.Abstractions.PermissionStatus.Granted)
{
var results = await CrossPermissions.Current.RequestPermissionsAsync(new[] { Plugin.Permissions.Abstractions.Permission.Storage });
storageStatus = results[Plugin.Permissions.Abstractions.Permission.Storage];
}
if (storageStatus == Plugin.Permissions.Abstractions.PermissionStatus.Granted)
{
try
{
if (File.Exists(recordingFileExternalPath)) // if file exists already in external storage
{
File.Delete(recordingFileExternalPath); // erase the file
}
File.Copy(filePath, recordingFileExternalPath); // and overwrite with new one
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
else
{
UserDialogs.Instance.Alert("Permission to write to External Storage denied, cannot save settings.", "Permission Denied", "Ok");
}
}
And then I would create a channel with that sound like this:
private void createCustomNotificationChannel()
{
try
{
// the custom channel
var urgentChannelName = GetString(Resource.String.noti_chan_custom);
var customChannelDescription = GetString(Resource.String.noti_chan_custom_description);
long[] customVibrationPattern = { 100, 30, 100, 30, 100, 200, 200, 30, 200, 30, 200, 200, 100, 30, 100, 30, 100, 100, 30, 100, 30, 100, 200, 200, 30, 200, 30, 200, 200, 100, 30, 100, 30, 100 };
// Creating an Audio Attribute
var alarmAttributes = new AudioAttributes.Builder()
.SetContentType(AudioContentType.Sonification)
.SetUsage(AudioUsageKind.Notification).Build();
var alarmSourcePath = System.IO.Path.Combine(Android.OS.Environment.ExternalStorageDirectory.Path, AppConstants.CUSTOM_ALERT_FILENAME);
// also tried here with other paths
// var alarmSourcePath = Path.Combine(FileSystem.AppDataDirectory, AppConstants.CUSTOM_ALERT_FILENAME);
// var alarmSourcePath = System.IO.Path.Combine(Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryAlarms).AbsolutePath, AppConstants.CUSTOM_ALERT_FILENAME);
var urgentAlarmUri = Android.Net.Uri.Parse(alarmSourcePath);
// checking here if file exists after voice recording and
// the bool is true
bool soundFileExists = File.Exists(alarmSourcePath);
var chan3 = new NotificationChannel(TERTIARY_CHANNEL_ID, urgentChannelName, NotificationImportance.High)
{
Description = customChannelDescription
};
// set the urgent channel properties
chan3.EnableLights(true);
chan3.LightColor = Android.Graphics.Color.Red;
chan3.SetSound(urgentAlarmUri, alarmAttributes);
chan3.EnableVibration(true);
chan3.SetVibrationPattern(customVibrationPattern);
chan3.SetBypassDnd(true);
chan3.LockscreenVisibility = NotificationVisibility.Public;
var manager = (NotificationManager)GetSystemService(NotificationService);
// create chan1 which is the urgent notifications channel
manager.CreateNotificationChannel(chan3);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Upvotes: 0
Views: 369
Reputation: 14991
To give users more control over their files and to limit file clutter, apps targeting Android 10 (API level 29) and higher are given scoped access into an external storage device, or scoped storage, by default. Such apps can see only their app-specific directory—accessed using Context.getExternalFilesDir()
—and specific types of media. It's a best practice to use scoped storage unless your app needs access to a file that doesn't reside in either the app-specific directory or the MediaStore
.you could refer to Manage scoped external storage access.
if you don't want change,you want to stick With What Worked Before.
For Android Q ,you could try to add android:requestLegacyExternalStorage="true"
to your <application>
element in the manifest.
Upvotes: 1