From 2d48517b7479409de726f49f66a04c9c0c0a76a1 Mon Sep 17 00:00:00 2001 From: sbosse Date: Mon, 21 Jul 2025 23:31:02 +0200 Subject: [PATCH] Mon 21 Jul 22:43:21 CEST 2025 --- .../src/ios/APPBackgroundMode.m | 283 ++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100644 js/ui/cordova/plugins/cordova-plugin-background-mode/src/ios/APPBackgroundMode.m diff --git a/js/ui/cordova/plugins/cordova-plugin-background-mode/src/ios/APPBackgroundMode.m b/js/ui/cordova/plugins/cordova-plugin-background-mode/src/ios/APPBackgroundMode.m new file mode 100644 index 0000000..d11dbd7 --- /dev/null +++ b/js/ui/cordova/plugins/cordova-plugin-background-mode/src/ios/APPBackgroundMode.m @@ -0,0 +1,283 @@ +/* + Copyright 2013-2017 appPlant GmbH + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +#import "APPMethodMagic.h" +#import "APPBackgroundMode.h" +#import + +@implementation APPBackgroundMode + +#pragma mark - +#pragma mark Constants + +NSString* const kAPPBackgroundJsNamespace = @"cordova.plugins.backgroundMode"; +NSString* const kAPPBackgroundEventActivate = @"activate"; +NSString* const kAPPBackgroundEventDeactivate = @"deactivate"; + + +#pragma mark - +#pragma mark Life Cycle + +/** + * Called by runtime once the Class has been loaded. + * Exchange method implementations to hook into their execution. + */ ++ (void) load +{ + [self swizzleWKWebViewEngine]; +} + +/** + * Initialize the plugin. + */ +- (void) pluginInitialize +{ + enabled = [self.class isRunningWebKit]; + [self configureAudioPlayer]; + [self configureAudioSession]; + [self observeLifeCycle]; +} + +/** + * Register the listener for pause and resume events. + */ +- (void) observeLifeCycle +{ + NSNotificationCenter* listener = [NSNotificationCenter + defaultCenter]; + + [listener addObserver:self + selector:@selector(keepAwake) + name:UIApplicationDidEnterBackgroundNotification + object:nil]; + + [listener addObserver:self + selector:@selector(stopKeepingAwake) + name:UIApplicationWillEnterForegroundNotification + object:nil]; + + if ([self.class isRunningWebKit]) + return; + + [listener addObserver:self + selector:@selector(handleAudioSessionInterruption:) + name:AVAudioSessionInterruptionNotification + object:nil]; +} + +#pragma mark - +#pragma mark Interface + +/** + * Enable the mode to stay awake + * when switching to background for the next time. + */ +- (void) enable:(CDVInvokedUrlCommand*)command +{ + if (enabled) + return; + + enabled = YES; + [self execCallback:command]; +} + +/** + * Disable the background mode + * and stop being active in background. + */ +- (void) disable:(CDVInvokedUrlCommand*)command +{ + if (!enabled || [self.class isRunningWebKit]) + return; + + enabled = NO; + [self stopKeepingAwake]; + [self execCallback:command]; +} + +#pragma mark - +#pragma mark Core + +/** + * Keep the app awake. + */ +- (void) keepAwake +{ + if (!enabled) + return; + + if (![self.class isRunningWebKit]) { + [audioPlayer play]; + } + + [self fireEvent:kAPPBackgroundEventActivate]; +} + +/** + * Let the app going to sleep. + */ +- (void) stopKeepingAwake +{ + if (TARGET_IPHONE_SIMULATOR) { + NSLog(@"BackgroundMode: On simulator apps never pause in background!"); + } + + if (audioPlayer.isPlaying || [self.class isRunningWebKit]) { + [self fireEvent:kAPPBackgroundEventDeactivate]; + } + + [audioPlayer pause]; +} + +/** + * Configure the audio player. + */ +- (void) configureAudioPlayer +{ + NSString* path = [[NSBundle mainBundle] + pathForResource:@"appbeep" ofType:@"wav"]; + + NSURL* url = [NSURL fileURLWithPath:path]; + + + audioPlayer = [[AVAudioPlayer alloc] + initWithContentsOfURL:url error:NULL]; + + audioPlayer.volume = 0; + audioPlayer.numberOfLoops = -1; +}; + +/** + * Configure the audio session. + */ +- (void) configureAudioSession +{ + AVAudioSession* session = [AVAudioSession + sharedInstance]; + + if ([self.class isRunningWebKit]) + return; + + // Don't activate the audio session yet + [session setActive:NO error:NULL]; + + // Play music even in background and dont stop playing music + // even another app starts playing sound + [session setCategory:AVAudioSessionCategoryPlayback + withOptions:AVAudioSessionCategoryOptionMixWithOthers + error:NULL]; + + // Active the audio session + [session setActive:YES error:NULL]; +}; + +#pragma mark - +#pragma mark Helper + +/** + * Simply invokes the callback without any parameter. + */ +- (void) execCallback:(CDVInvokedUrlCommand*)command +{ + CDVPluginResult *result = [CDVPluginResult + resultWithStatus:CDVCommandStatus_OK]; + + [self.commandDelegate sendPluginResult:result + callbackId:command.callbackId]; +} + +/** + * Restart playing sound when interrupted by phone calls. + */ +- (void) handleAudioSessionInterruption:(NSNotification*)notification +{ + [self fireEvent:kAPPBackgroundEventDeactivate]; + [self keepAwake]; +} + +/** + * Find out if the app runs inside the webkit powered webview. + */ ++ (BOOL) isRunningWebKit +{ + return IsAtLeastiOSVersion(@"8.0") && NSClassFromString(@"CDVWKWebViewEngine"); +} + +/** + * Method to fire an event with some parameters in the browser. + */ +- (void) fireEvent:(NSString*)event +{ + NSString* active = + [event isEqualToString:kAPPBackgroundEventActivate] ? @"true" : @"false"; + + NSString* flag = [NSString stringWithFormat:@"%@._isActive=%@;", + kAPPBackgroundJsNamespace, active]; + + NSString* depFn = [NSString stringWithFormat:@"%@.on%@();", + kAPPBackgroundJsNamespace, event]; + + NSString* fn = [NSString stringWithFormat:@"%@.fireEvent('%@');", + kAPPBackgroundJsNamespace, event]; + + NSString* js = [NSString stringWithFormat:@"%@%@%@", flag, depFn, fn]; + + [self.commandDelegate evalJs:js]; +} + +#pragma mark - +#pragma mark Swizzling + +/** + * Method to swizzle. + */ ++ (NSString*) wkProperty +{ + NSString* str = @"X2Fsd2F5c1J1bnNBdEZvcmVncm91bmRQcmlvcml0eQ=="; + NSData* data = [[NSData alloc] initWithBase64EncodedString:str options:0]; + + return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; +} + +/** + * Swizzle some implementations of CDVWKWebViewEngine. + */ ++ (void) swizzleWKWebViewEngine +{ + if (![self isRunningWebKit]) + return; + + Class wkWebViewEngineCls = NSClassFromString(@"CDVWKWebViewEngine"); + SEL selector = NSSelectorFromString(@"createConfigurationFromSettings:"); + + SwizzleSelectorWithBlock_Begin(wkWebViewEngineCls, selector) + ^(CDVPlugin *self, NSDictionary *settings) { + id obj = ((id (*)(id, SEL, NSDictionary*))_imp)(self, _cmd, settings); + + [obj setValue:[NSNumber numberWithBool:YES] + forKey:[APPBackgroundMode wkProperty]]; + + return obj; + } + SwizzleSelectorWithBlock_End; +} + +@end