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:
parent
15fab2baf3
commit
4715240cc1
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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
|
||||
},
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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";
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user