Added custom notification sounds

* user can add custom notification sounds from Files app (aiff, wav, or caf files)
* former OWSSound enum is now named OWSStandardSound, OWSSound turned into an alias for NSUInteger
* custom sounds get IDs based on the hash of the file name
This commit is contained in:
Christopher Kobusch 2020-09-15 21:31:47 +02:00 committed by Nora Trapp
parent 15fab2baf3
commit 4715240cc1
10 changed files with 323 additions and 119 deletions

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
//
#import "OWSTableViewController.h"

View File

@ -12,7 +12,7 @@
NS_ASSUME_NONNULL_BEGIN
@interface OWSSoundSettingsViewController ()
@interface OWSSoundSettingsViewController () <UIDocumentPickerDelegate>
@property (nonatomic) BOOL isDirty;
@ -83,12 +83,12 @@ NS_ASSUME_NONNULL_BEGIN
NSArray<NSNumber *> *allSounds = [OWSSounds allNotificationSounds];
for (NSNumber *nsValue in allSounds) {
OWSSound sound = (OWSSound)nsValue.intValue;
OWSSound sound = (OWSSound)nsValue.unsignedLongValue;
OWSTableItem *item;
NSString *soundLabelText = ^{
NSString *baseName = [OWSSounds displayNameForSound:sound];
if (sound == OWSSound_Note) {
if (sound == OWSStandardSound_Note) {
NSString *noteStringFormat = NSLocalizedString(@"SETTINGS_AUDIO_DEFAULT_TONE_LABEL_FORMAT",
@"Format string for the default 'Note' sound. Embeds the system {{sound name}}.");
return [NSString stringWithFormat:noteStringFormat, baseName];
@ -115,6 +115,24 @@ NS_ASSUME_NONNULL_BEGIN
[soundsSection addItem:item];
}
NSString *addCustomSoundItemTitle = NSLocalizedString(@"NOTIFICATIONS_SECTION_SOUNDS_ADD_CUSTOM_SOUND",
@"Label for settings UI that allows user to add a new notification sound.");
OWSTableItem *addCustomSoundItem =
[OWSTableItem disclosureItemWithText:addCustomSoundItemTitle
actionBlock:^{
UIDocumentPickerViewController *picker = [[UIDocumentPickerViewController alloc]
initWithDocumentTypes:@[
@"com.microsoft.waveform-audio",
@"public.aifc-audio",
@"public.aiff-audio",
@"com.apple.coreaudio-format"
]
inMode:UIDocumentPickerModeImport];
picker.delegate = weakSelf;
[weakSelf presentViewController:picker animated:true completion:nil];
}];
[soundsSection addItem:addCustomSoundItem];
[contents addSection:soundsSection];
self.contents = contents;
@ -159,6 +177,12 @@ NS_ASSUME_NONNULL_BEGIN
[self.navigationController popViewControllerAnimated:YES];
}
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls
{
[OWSSounds importSoundsAtURLs:urls];
[self updateTableContents];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -3112,7 +3112,7 @@ typedef enum : NSUInteger {
self.inputToolbar.quotedReply = nil;
if ([Environment.shared.preferences soundInForeground]) {
SystemSoundID soundId = [OWSSounds systemSoundIDForSound:OWSSound_MessageSent quiet:YES];
SystemSoundID soundId = [OWSSounds systemSoundIDForSound:OWSStandardSound_MessageSent quiet:YES];
AudioServicesPlaySystemSound(soundId);
}
[self.typingIndicators didSendOutgoingMessageInThread:self.thread];

View File

@ -366,7 +366,7 @@ extension ConversationSettingsViewController {
let cell = OWSTableItem.buildCellWithAccessoryLabel(icon: .settingsMessageSound,
itemName: NSLocalizedString("SETTINGS_ITEM_NOTIFICATION_SOUND",
comment: "Label for settings view that allows user to change the notification sound."),
accessoryText: OWSSounds.displayName(for: sound))
accessoryText: OWSSounds.displayName(forSound: sound))
cell.accessibilityIdentifier = UIView.accessibilityIdentifier(in: self, name: "notifications")
return cell
},

View File

@ -203,7 +203,7 @@ protocol CallAudioServiceDelegate: class {
// HACK: Without this async, dialing sound only plays once. I don't really understand why. Does the audioSession
// need some time to settle? Is somethign else interrupting our session?
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.2) {
self.play(sound: OWSSound.callConnecting)
self.play(sound: .callConnecting)
}
}
@ -216,7 +216,7 @@ protocol CallAudioServiceDelegate: class {
AssertIsOnMainThread()
Logger.debug("")
self.play(sound: OWSSound.callOutboundRinging)
self.play(sound: .callOutboundRinging)
}
private func handleLocalRinging(call: SignalCall) {
@ -266,7 +266,7 @@ protocol CallAudioServiceDelegate: class {
AssertIsOnMainThread()
Logger.debug("")
play(sound: OWSSound.callBusy)
play(sound: .callBusy)
// Let the busy sound play for 4 seconds. The full file is longer than necessary
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 4.0) {
@ -310,12 +310,12 @@ protocol CallAudioServiceDelegate: class {
stopRinging()
}
private func prepareToPlay(sound: OWSSound) -> OWSAudioPlayer? {
guard let newPlayer = OWSSounds.audioPlayer(for: sound, audioBehavior: .call) else {
owsFailDebug("unable to build player for sound: \(OWSSounds.displayName(for: sound))")
private func prepareToPlay(sound: OWSStandardSound) -> OWSAudioPlayer? {
guard let newPlayer = OWSSounds.audioPlayer(forSound: sound.rawValue, audioBehavior: .call) else {
owsFailDebug("unable to build player for sound: \(OWSSounds.displayName(forSound: sound.rawValue))")
return nil
}
Logger.info("playing sound: \(OWSSounds.displayName(for: sound))")
Logger.info("playing sound: \(OWSSounds.displayName(forSound: sound.rawValue))")
// It's important to stop the current player **before** starting the new player. In the case that
// we're playing the same sound, since the player is memoized on the sound instance, we'd otherwise
@ -326,7 +326,7 @@ protocol CallAudioServiceDelegate: class {
return newPlayer
}
private func play(sound: OWSSound) {
private func play(sound: OWSStandardSound) {
guard let newPlayer = prepareToPlay(sound: sound) else { return }
newPlayer.play()
}

View File

@ -2716,6 +2716,9 @@
/* Label for settings UI that allows user to change the notification sound. */
"NOTIFICATIONS_SECTION_SOUNDS" = "Sounds";
/* Label for settings UI that allows user to add a new notification sound. */
"NOTIFICATIONS_SECTION_SOUNDS_ADD_CUSTOM_SOUND" = "Add custom sound…";
/* No comment provided by engineer. */
"NOTIFICATIONS_SENDER_AND_MESSAGE" = "Name, Content, and Actions";

View File

@ -125,7 +125,7 @@ extension UserNotificationPresenterAdaptee: NotificationPresenterAdaptee {
content.categoryIdentifier = category.identifier
content.userInfo = userInfo
let isAppActive = CurrentAppContext().isMainAppAndActive
if let sound = sound, sound != OWSSound.none {
if let sound = sound, sound != OWSStandardSound.none.rawValue {
content.sound = sound.notificationSound(isQuiet: isAppActive)
}
@ -316,7 +316,7 @@ public protocol ConversationSplit {
extension OWSSound {
func notificationSound(isQuiet: Bool) -> UNNotificationSound {
guard let filename = OWSSounds.filename(for: self, quiet: isQuiet) else {
guard let filename = OWSSounds.filename(forSound: self, quiet: isQuiet) else {
owsFailDebug("filename was unexpectedly nil")
return UNNotificationSound.default
}

View File

@ -7,38 +7,43 @@
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSUInteger, OWSSound) {
OWSSound_Default = 0,
typedef NSUInteger OWSSound;
typedef NS_ENUM(NSUInteger, OWSStandardSound) {
OWSStandardSound_Default = 0,
// Notification Sounds
OWSSound_Aurora,
OWSSound_Bamboo,
OWSSound_Chord,
OWSSound_Circles,
OWSSound_Complete,
OWSSound_Hello,
OWSSound_Input,
OWSSound_Keys,
OWSSound_Note,
OWSSound_Popcorn,
OWSSound_Pulse,
OWSSound_Synth,
OWSSound_SignalClassic,
OWSStandardSound_Aurora,
OWSStandardSound_Bamboo,
OWSStandardSound_Chord,
OWSStandardSound_Circles,
OWSStandardSound_Complete,
OWSStandardSound_Hello,
OWSStandardSound_Input,
OWSStandardSound_Keys,
OWSStandardSound_Note,
OWSStandardSound_Popcorn,
OWSStandardSound_Pulse,
OWSStandardSound_Synth,
OWSStandardSound_SignalClassic,
// Ringtone Sounds
OWSSound_Reflection,
OWSStandardSound_Reflection,
// Calls
OWSSound_CallConnecting,
OWSSound_CallOutboundRinging,
OWSSound_CallBusy,
OWSSound_CallEnded,
OWSStandardSound_CallConnecting,
OWSStandardSound_CallOutboundRinging,
OWSStandardSound_CallBusy,
OWSStandardSound_CallEnded,
// Other
OWSSound_MessageSent,
OWSSound_None,
OWSSound_Silence,
OWSSound_DefaultiOSIncomingRingtone = OWSSound_Reflection,
OWSStandardSound_MessageSent,
OWSStandardSound_None,
OWSStandardSound_Silence,
OWSStandardSound_DefaultiOSIncomingRingtone = OWSStandardSound_Reflection,
// Custom sound IDs begin at this threshold
OWSStandardSound_CustomThreshold = 1 << 16, // 16 == OWSCustomSoundShift
};
@class OWSAudioPlayer;
@ -55,6 +60,9 @@ typedef NS_ENUM(NSUInteger, OWSSound) {
+ (nullable NSString *)filenameForSound:(OWSSound)sound;
+ (nullable NSString *)filenameForSound:(OWSSound)sound quiet:(BOOL)quiet;
+ (void)importSoundsAtURLs:(NSArray<NSURL *> *)urls;
+ (NSString *)soundsDirectory;
#pragma mark - Notifications
+ (NSArray<NSNumber *> *)allNotificationSounds;

View File

@ -5,12 +5,17 @@
#import "OWSSounds.h"
#import "Environment.h"
#import "OWSAudioPlayer.h"
#import <SignalCoreKit/Cryptography.h>
#import <SignalMessaging/SignalMessaging-Swift.h>
#import <SignalServiceKit/OWSFileSystem.h>
#import <SignalServiceKit/SignalServiceKit-Swift.h>
#import <SignalServiceKit/TSThread.h>
NSString *const kOWSSoundsStorageGlobalNotificationKey = @"kOWSSoundsStorageGlobalNotificationKey";
// This name is specified in the payload by the Signal Service when requesting fallback push notifications.
NSString *const kDefaultNotificationSoundFilename = @"NewMessage.aifc";
const NSUInteger OWSCustomSoundShift = 16;
@interface OWSSystemSound : NSObject
@ -100,9 +105,43 @@ NSString *const kOWSSoundsStorageGlobalNotificationKey = @"kOWSSoundsStorageGlob
OWSSingletonAssert();
[AppReadiness runNowOrWhenAppDidBecomeReadyPolite:^{ [OWSSounds cleanupOrphanedSounds]; }];
return self;
}
+ (void)cleanupOrphanedSounds
{
NSSet<NSNumber *> *allCustomSounds = [NSSet setWithArray:[self allCustomNotificationSounds]];
if (allCustomSounds.count == 0) {
return;
}
__block NSSet<NSNumber *> *allInUseSounds;
[self.databaseStorage readWithBlock:^(SDSAnyReadTransaction *transaction) {
allInUseSounds = [NSSet setWithArray:[self.keyValueStore allValuesWithTransaction:transaction]];
}];
NSMutableSet *orphanedSounds = [allCustomSounds mutableCopy];
[orphanedSounds minusSet:allInUseSounds];
if (orphanedSounds.count == 0) {
return;
}
NSUInteger deletedSoundCount = 0;
for (NSNumber *soundNumber in orphanedSounds) {
OWSSound sound = soundNumber.unsignedLongValue;
if ([self deleteCustomSound:sound]) {
deletedSoundCount++;
} else {
OWSFailDebug(@"Failed to delete orphaned sound.");
}
}
OWSLogInfo(@"Cleaned up %lu orphaned custom sounds.", deletedSoundCount);
}
+ (SDSKeyValueStore *)keyValueStore
{
NSString *const kOWSSoundsStorageNotificationCollection = @"kOWSSoundsStorageNotificationCollection";
@ -111,83 +150,87 @@ NSString *const kOWSSoundsStorageGlobalNotificationKey = @"kOWSSoundsStorageGlob
+ (NSArray<NSNumber *> *)allNotificationSounds
{
return @[
return [@[
// None and Note (default) should be first.
@(OWSSound_None),
@(OWSSound_Note),
@(OWSStandardSound_None),
@(OWSStandardSound_Note),
@(OWSSound_Aurora),
@(OWSSound_Bamboo),
@(OWSSound_Chord),
@(OWSSound_Circles),
@(OWSSound_Complete),
@(OWSSound_Hello),
@(OWSSound_Input),
@(OWSSound_Keys),
@(OWSSound_Popcorn),
@(OWSSound_Pulse),
@(OWSSound_SignalClassic),
@(OWSSound_Synth),
];
@(OWSStandardSound_Aurora),
@(OWSStandardSound_Bamboo),
@(OWSStandardSound_Chord),
@(OWSStandardSound_Circles),
@(OWSStandardSound_Complete),
@(OWSStandardSound_Hello),
@(OWSStandardSound_Input),
@(OWSStandardSound_Keys),
@(OWSStandardSound_Popcorn),
@(OWSStandardSound_Pulse),
@(OWSStandardSound_SignalClassic),
@(OWSStandardSound_Synth),
] arrayByAddingObjectsFromArray:[OWSSounds allCustomNotificationSounds]];
}
+ (NSString *)displayNameForSound:(OWSSound)sound
{
// TODO: Should we localize these sound names?
switch (sound) {
case OWSSound_Default:
case OWSStandardSound_Default:
OWSFailDebug(@"invalid argument.");
return @"";
// Notification Sounds
case OWSSound_Aurora:
case OWSStandardSound_Aurora:
return @"Aurora";
case OWSSound_Bamboo:
case OWSStandardSound_Bamboo:
return @"Bamboo";
case OWSSound_Chord:
case OWSStandardSound_Chord:
return @"Chord";
case OWSSound_Circles:
case OWSStandardSound_Circles:
return @"Circles";
case OWSSound_Complete:
case OWSStandardSound_Complete:
return @"Complete";
case OWSSound_Hello:
case OWSStandardSound_Hello:
return @"Hello";
case OWSSound_Input:
case OWSStandardSound_Input:
return @"Input";
case OWSSound_Keys:
case OWSStandardSound_Keys:
return @"Keys";
case OWSSound_Note:
case OWSStandardSound_Note:
return @"Note";
case OWSSound_Popcorn:
case OWSStandardSound_Popcorn:
return @"Popcorn";
case OWSSound_Pulse:
case OWSStandardSound_Pulse:
return @"Pulse";
case OWSSound_Synth:
case OWSStandardSound_Synth:
return @"Synth";
case OWSSound_SignalClassic:
case OWSStandardSound_SignalClassic:
return @"Signal Classic";
// Call Audio
case OWSSound_Reflection:
case OWSStandardSound_Reflection:
return @"Opening";
case OWSSound_CallConnecting:
case OWSStandardSound_CallConnecting:
return @"Call Connecting";
case OWSSound_CallOutboundRinging:
case OWSStandardSound_CallOutboundRinging:
return @"Call Outboung Ringing";
case OWSSound_CallBusy:
case OWSStandardSound_CallBusy:
return @"Call Busy";
case OWSSound_CallEnded:
case OWSStandardSound_CallEnded:
return @"Call Ended";
case OWSSound_MessageSent:
case OWSStandardSound_MessageSent:
return @"Message Sent";
case OWSSound_Silence:
case OWSStandardSound_Silence:
return @"Silence";
// Other
case OWSSound_None:
case OWSStandardSound_None:
return NSLocalizedString(@"SOUNDS_NONE",
@"Label for the 'no sound' option that allows users to disable sounds for notifications, "
@"etc.");
// Custom Sounds
default:
return [OWSSounds displayNameForCustomSound:sound];
}
}
@ -199,72 +242,80 @@ NSString *const kOWSSoundsStorageGlobalNotificationKey = @"kOWSSoundsStorageGlob
+ (nullable NSString *)filenameForSound:(OWSSound)sound quiet:(BOOL)quiet
{
switch (sound) {
case OWSSound_Default:
case OWSStandardSound_Default:
OWSFailDebug(@"invalid argument.");
return @"";
// Notification Sounds
case OWSSound_Aurora:
case OWSStandardSound_Aurora:
return (quiet ? @"aurora-quiet.aifc" : @"aurora.aifc");
case OWSSound_Bamboo:
case OWSStandardSound_Bamboo:
return (quiet ? @"bamboo-quiet.aifc" : @"bamboo.aifc");
case OWSSound_Chord:
case OWSStandardSound_Chord:
return (quiet ? @"chord-quiet.aifc" : @"chord.aifc");
case OWSSound_Circles:
case OWSStandardSound_Circles:
return (quiet ? @"circles-quiet.aifc" : @"circles.aifc");
case OWSSound_Complete:
case OWSStandardSound_Complete:
return (quiet ? @"complete-quiet.aifc" : @"complete.aifc");
case OWSSound_Hello:
case OWSStandardSound_Hello:
return (quiet ? @"hello-quiet.aifc" : @"hello.aifc");
case OWSSound_Input:
case OWSStandardSound_Input:
return (quiet ? @"input-quiet.aifc" : @"input.aifc");
case OWSSound_Keys:
case OWSStandardSound_Keys:
return (quiet ? @"keys-quiet.aifc" : @"keys.aifc");
case OWSSound_Note:
case OWSStandardSound_Note:
return (quiet ? @"note-quiet.aifc" : @"note.aifc");
case OWSSound_Popcorn:
case OWSStandardSound_Popcorn:
return (quiet ? @"popcorn-quiet.aifc" : @"popcorn.aifc");
case OWSSound_Pulse:
case OWSStandardSound_Pulse:
return (quiet ? @"pulse-quiet.aifc" : @"pulse.aifc");
case OWSSound_Synth:
case OWSStandardSound_Synth:
return (quiet ? @"synth-quiet.aifc" : @"synth.aifc");
case OWSSound_SignalClassic:
case OWSStandardSound_SignalClassic:
return (quiet ? @"classic-quiet.aifc" : @"classic.aifc");
// Ringtone Sounds
case OWSSound_Reflection:
case OWSStandardSound_Reflection:
return @"Reflection.m4r";
// Calls
case OWSSound_CallConnecting:
case OWSStandardSound_CallConnecting:
return @"ringback_tone_ansi.caf";
case OWSSound_CallOutboundRinging:
case OWSStandardSound_CallOutboundRinging:
return @"ringback_tone_ansi.caf";
case OWSSound_CallBusy:
case OWSStandardSound_CallBusy:
return @"busy_tone_ansi.caf";
case OWSSound_CallEnded:
case OWSStandardSound_CallEnded:
return @"end_call_tone_cept.caf";
case OWSSound_MessageSent:
case OWSStandardSound_MessageSent:
return @"message_sent.aiff";
case OWSSound_Silence:
case OWSStandardSound_Silence:
return @"silence.aiff";
// Other
case OWSSound_None:
case OWSStandardSound_None:
return nil;
// Custom Sounds
default:
return [OWSSounds filenameForCustomSound:sound];
}
}
+ (nullable NSURL *)soundURLForSound:(OWSSound)sound quiet:(BOOL)quiet
{
NSString *_Nullable filename = [self filenameForSound:sound quiet:quiet];
if (!filename) {
return nil;
if (sound < OWSStandardSound_CustomThreshold) {
NSString *_Nullable filename = [self filenameForSound:sound quiet:quiet];
if (!filename) {
return nil;
}
NSURL *_Nullable url = [[NSBundle mainBundle] URLForResource:filename.stringByDeletingPathExtension
withExtension:filename.pathExtension];
OWSAssertDebug(url);
return url;
} else {
return [OWSSounds soundURLForCustomSound:sound];
}
NSURL *_Nullable url = [[NSBundle mainBundle] URLForResource:filename.stringByDeletingPathExtension
withExtension:filename.pathExtension];
OWSAssertDebug(url);
return url;
}
+ (SystemSoundID)systemSoundIDForSound:(OWSSound)sound quiet:(BOOL)quiet
@ -289,11 +340,49 @@ NSString *const kOWSSoundsStorageGlobalNotificationKey = @"kOWSSoundsStorageGlob
return newSound.soundID;
}
+ (void)importSoundsAtURLs:(NSArray<NSURL *> *)urls
{
NSFileManager *fileManager = [NSFileManager defaultManager];
for (NSURL *url in urls) {
NSError *error = NULL;
NSString *filename = url.lastPathComponent;
if (!filename)
continue;
NSString *destination = [NSString pathWithComponents:@[ [OWSSounds soundsDirectory], filename ]];
if (![fileManager fileExistsAtPath:destination]) {
[fileManager copyItemAtPath:[url path] toPath:destination error:&error];
}
if (error) {
OWSFailDebug(@"Failed to import custom sound with error %@", [error localizedDescription]);
error = NULL;
}
}
}
+ (BOOL)deleteCustomSound:(OWSSound)sound
{
if (sound < OWSStandardSound_CustomThreshold) {
OWSFailDebug(@"Can't delete built-in sound");
return NO;
} else {
NSURL *url = [self soundURLForCustomSound:sound];
NSError *error;
[OWSFileSystem deleteFileIfExistsWithUrl:url error:&error];
if (error) {
OWSFailDebug(@"Failed to delete custom sound: %@", error);
}
return YES;
}
}
#pragma mark - Notifications
+ (OWSSound)defaultNotificationSound
{
return OWSSound_Note;
return OWSStandardSound_Note;
}
+ (OWSSound)globalNotificationSound
@ -303,7 +392,7 @@ NSString *const kOWSSoundsStorageGlobalNotificationKey = @"kOWSSoundsStorageGlob
value = [self.keyValueStore getNSNumber:kOWSSoundsStorageGlobalNotificationKey transaction:transaction];
}];
// Default to the global default.
return (value ? (OWSSound)value.intValue : [self defaultNotificationSound]);
return (value ? (OWSSound)value.unsignedLongValue : [self defaultNotificationSound]);
}
+ (void)setGlobalNotificationSound:(OWSSound)sound
@ -332,11 +421,8 @@ NSString *const kOWSSoundsStorageGlobalNotificationKey = @"kOWSSoundsStorageGlob
// Fallback push notifications play a sound specified by the server, but we don't want to store this configuration
// on the server. Instead, we create a file with the same name as the default to be played when receiving
// a fallback notification.
NSString *dirPath = [[OWSFileSystem appLibraryDirectoryPath] stringByAppendingPathComponent:@"Sounds"];
[OWSFileSystem ensureDirectoryExists:dirPath];
NSString *dirPath = [OWSSounds soundsDirectory];
// This name is specified in the payload by the Signal Service when requesting fallback push notifications.
NSString *kDefaultNotificationSoundFilename = @"NewMessage.aifc";
NSString *defaultSoundPath = [dirPath stringByAppendingPathComponent:kDefaultNotificationSoundFilename];
OWSLogDebug(@"writing new default sound to %@", defaultSoundPath);
@ -347,7 +433,7 @@ NSString *const kOWSSoundsStorageGlobalNotificationKey = @"kOWSSoundsStorageGlob
if (soundURL) {
return [NSData dataWithContentsOfURL:soundURL];
} else {
OWSAssertDebug(sound == OWSSound_None);
OWSAssertDebug(sound == OWSStandardSound_None);
return [NSData new];
}
}();
@ -375,7 +461,7 @@ NSString *const kOWSSoundsStorageGlobalNotificationKey = @"kOWSSoundsStorageGlob
value = [self.keyValueStore getNSNumber:thread.uniqueId transaction:transaction];
}];
// Default to the "global" notification sound, which in turn will default to the global default.
return (value ? (OWSSound)value.intValue : [self globalNotificationSound]);
return (value ? (OWSSound)value.unsignedLongValue : [self globalNotificationSound]);
}
+ (void)setNotificationSound:(OWSSound)sound forThread:(TSThread *)thread
@ -385,12 +471,95 @@ NSString *const kOWSSoundsStorageGlobalNotificationKey = @"kOWSSoundsStorageGlob
});
}
#pragma mark - Custom Sounds
+ (NSString *)displayNameForCustomSound:(OWSSound)sound
{
NSString *filename = [OWSSounds filenameForCustomSound:sound];
NSString *fileNameWithoutExtension = [[filename lastPathComponent] stringByDeletingPathExtension];
if (!fileNameWithoutExtension) {
OWSFailDebug(@"Unable to retrieve custom sound display name from: %lu", sound);
return @"Custom Sound";
}
return [fileNameWithoutExtension capitalizedString];
}
+ (nullable NSString *)filenameForCustomSound:(OWSSound)sound
{
NSError *error = NULL;
NSArray *customSoundFilenames =
[[NSFileManager defaultManager] contentsOfDirectoryAtPath:[OWSSounds soundsDirectory] error:&error];
if (error) {
OWSFailDebug(@"Failed retrieving custom sound files: %@", error.localizedDescription);
return NULL;
}
NSUInteger index =
[customSoundFilenames indexOfObjectPassingTest:^BOOL(id _Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
return [OWSSounds customSoundForFilename:obj] == sound;
}];
return index == NSNotFound ? NULL : [customSoundFilenames objectAtIndex:index];
}
+ (NSArray<NSNumber *> *)allCustomNotificationSounds
{
NSError *error = NULL;
NSArray *customSoundFilenames =
[[NSFileManager defaultManager] contentsOfDirectoryAtPath:[OWSSounds soundsDirectory] error:&error];
if (error) {
OWSFailDebug(@"Failed retrieving custom sound files: %@", error.localizedDescription);
return @[];
}
NSMutableArray<NSNumber *> *sounds = [[NSMutableArray alloc] initWithCapacity:customSoundFilenames.count];
for (NSString *filename in customSoundFilenames) {
if ([filename isEqualToString:kDefaultNotificationSoundFilename]) {
continue;
}
[sounds addObject:[NSNumber numberWithUnsignedLong:[OWSSounds customSoundForFilename:filename]]];
}
return sounds;
}
+ (nullable NSURL *)soundURLForCustomSound:(OWSSound)sound
{
NSString *path =
[[OWSSounds soundsDirectory] stringByAppendingPathComponent:[OWSSounds filenameForCustomSound:sound]];
return [NSURL fileURLWithPath:path];
}
+ (OWSSound)customSoundForFilename:(NSString *)filename
{
NSUInteger hashValue = 0;
NSData *_Nullable filenameData = [filename dataUsingEncoding:NSUTF8StringEncoding];
if (!filenameData) {
OWSFailDebug(@"could not get data from filename.");
return OWSStandardSound_Default;
}
NSData *_Nullable hashData = [Cryptography computeSHA256Digest:filenameData truncatedToBytes:sizeof(hashValue)];
if (!hashData) {
OWSFailDebug(@"could not get hash from filename.");
return OWSStandardSound_Default;
}
[hashData getBytes:&hashValue length:sizeof(hashValue)];
return hashValue << OWSCustomSoundShift;
}
+ (NSString *)soundsDirectory
{
NSString *directory = [[OWSFileSystem appLibraryDirectoryPath] stringByAppendingPathComponent:@"Sounds"];
[OWSFileSystem ensureDirectoryExists:directory];
return directory;
}
#pragma mark - AudioPlayer
+ (BOOL)shouldAudioPlayerLoopForSound:(OWSSound)sound
{
return (sound == OWSSound_CallConnecting || sound == OWSSound_CallOutboundRinging
|| sound == OWSSound_DefaultiOSIncomingRingtone);
return (sound == OWSStandardSound_CallConnecting || sound == OWSStandardSound_CallOutboundRinging
|| sound == OWSStandardSound_DefaultiOSIncomingRingtone);
}
+ (nullable OWSAudioPlayer *)audioPlayerForSound:(OWSSound)sound audioBehavior:(OWSAudioBehavior)audioBehavior

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
//
#import "OWS107LegacySounds.h"
@ -23,7 +23,7 @@ static NSString *const OWS107LegacySoundsMigrationId = @"107";
{
OWSAssertDebug(transaction);
[OWSSounds setGlobalNotificationSound:OWSSound_SignalClassic transaction:transaction.asAnyWrite];
[OWSSounds setGlobalNotificationSound:OWSStandardSound_SignalClassic transaction:transaction.asAnyWrite];
}
@end