I have some data saved with preference bundle controller class. I want to share it with its tweak file i.e. I want springboard to be able to communicate preference bundle in order to get some data from it and vice versa. I have tried to use Libobjcipc - as written in its documentation:
libobjcipc is a developer library that provides an inter-process communication (between app and SpringBoard) solution for jailbroken iOS. It handles the socket connections between SpringBoard and app processes...
I have tried to send message from tweak, but unable to figure it out how to receive incoming message in my preference bundle controller. I know the method [OBJCIPC registerIncomingMessageFromSpringBoardHandlerForMessageName:handler:] will be used for this purpose. But where do I use it? Hope for some positive response... Thanks..
EDIT:
I have tried using CFNotificationCenter after suggestion of #creker. I can send notification, but my tweak cannot receive it, and so callback method callBackNotification isn't executed. Here is changed code:
Tweak.xm
//at the end of tweak file
static void callBackNotification(CFNotificationCenterRef center,void *observer,CFStringRef name,const void *object,CFDictionaryRef userInfo)
{
NSLog(#"Notification received!");
}
%ctor
{
NSLog(#"Init cqlled!"); //this piece of code called successfully
//Register for the change notification
CFNotificationCenterRef r = CFNotificationCenterGetDarwinNotifyCenter();
CFNotificationCenterAddObserver(r, NULL, reloadPrefsNotification, CFSTR("ccom.identifier.message"), NULL, CFNotificationSuspensionBehaviorDeliverImmediately);
}
Preference Bundle Controller
#interface SettingsListController: PSListController {
}
#end
#implementation SettingsListController
- (id)specifiers {
if(_specifiers == nil) {
_specifiers = [[self loadSpecifiersFromPlistName:#"JRLockerSettings" target:self] retain];
}
return _specifiers;
}
-(void) postNotification()
{
CFNotificationCenterRef center = CFNotificationCenterGetLocalCenter();
// post a notification
CFDictionaryKeyCallBacks keyCallbacks = {0, NULL, NULL, CFCopyDescription, CFEqual, NULL};
CFDictionaryValueCallBacks valueCallbacks = {0, NULL, NULL, CFCopyDescription, CFEqual};
CFMutableDictionaryRef dictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 1,
&keyCallbacks, &valueCallbacks);
CFDictionaryAddValue(dictionary, CFSTR("identifier"), CFSTR("value"));
CFNotificationCenterPostNotification(center, CFSTR("com.identifier.message"), NULL, dictionary, TRUE);
CFRelease(dictionary);
}
#end
Related
I have used Flutter Blue for a college work, where I need to create an application to fetch and pass information to an equipment. The passing of this data must be automatic, as in any application (after all the end user should not look for the services and characteristics necessary to carry out the process). The problem is that I am not being able to perform the data passing soon after connecting with the device.
I'm using the App example I downloaded at https://github.com/pauldemarco/flutter_blue, so the basic idea is that as soon as I connect to my bluetooth device I send a message to a certain device. There is already an answered question that has the interest of setting notifications when connecting at Flutter Blue Setting Notifications
I followed the same example but instead of using _setNotification (c) I used the _writeCharacteristic (c), but it does not work.
_connect(BluetoothDevice d) async {
device = d;
// Connect to device
deviceConnection = _flutterBlue
.connect(device, timeout: const Duration(seconds: 4))
.listen(
null,
onDone: _disconnect,
);
// Update the connection state immediately
device.state.then((s) {
setState(() {
deviceState = s;
});
});
// Subscribe to connection changes
deviceStateSubscription = device.onStateChanged().listen((s) {
setState(() {
deviceState = s;
});
if (s == BluetoothDeviceState.connected) {
device.discoverServices().then((s) {
services = s;
for(BluetoothService service in services) {
for(BluetoothCharacteristic c in service.characteristics) {
if(c.uuid == new Guid("06d1e5e7-79ad-4a71-8faa-373789f7d93c")) {
_writeCharacteristic(c);
} else {
print("Nope");
}
}
}
setState(() {
services = s;
});
});
}
});
}
I have changed the original code so that it prints me the notifications as soon as I perform the writing method. The notifications should show me a standard message that is in the firmware of the device, but instead it is printing me the Local Name of the bluetooth chip, being that if I select the service and characteristic manually the return is the correct message.
You'd need to elaborate how you're executing writes on the descriptor - inside _writeCharacteristic(c).
BluetoothDescriptor.write() is a Future per docs, you should be able to catch any errors thrown during write.
I am trying to get push notifications to work on iOS, but I can not get access to the device token!
My Unity version is 5.4.1f1.
I have enabled the Push Notifications capability in XCode and all the certificates are setup correctly:
In my script in the start method I call this:
UnityEngine.iOS.NotificationServices.RegisterForNotifications
(UnityEngine.iOS.NotificationType.Alert | UnityEngine.iOS.NotificationType.Badge
| UnityEngine.iOS.NotificationType.Sound, true);
Then from the update method I call this method:
private bool RegisterTokenWithPlayfab( System.Action successCallback,
System.Action<PlayFabError> errorCallback )
{
byte[] token = UnityEngine.iOS.NotificationServices.deviceToken;
if(token != null)
{
// Registration on backend
}
else
{
string errorDescription = UnityEngine.iOS.NotificationServices.registrationError;
Debug.Log( "Push Notifications Registration failed with: " + errorDescription );
return false;
}
}
The token keeps being empty, so the else-branch is entered every call. Also, the registrationError keeps being empty.
Can someone point me in the right direction on this? What else can I try or how can I get more infos on what is going wrong??
Try this one
Go to your application target. Choose Capabilities and ensure that ‘Push Notifications’ is enabled there.
You need to check deviceToken in a Coroutine or Update.
using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
using NotificationServices = UnityEngine.iOS.NotificationServices;
using NotificationType = UnityEngine.iOS.NotificationType;
public class NotificationRegistrationExample : MonoBehaviour
{
bool tokenSent;
void Start()
{
tokenSent = false;
NotificationServices.RegisterForNotifications(
NotificationType.Alert |
NotificationType.Badge |
NotificationType.Sound, true);
}
void Update()
{
if (!tokenSent)
{
byte[] token = NotificationServices.deviceToken;
if (token != null)
{
// send token to a provider
string token = System.BitConverter.ToString(token).Replace('-', '%');
Debug.Log(token)
tokenSent = true;
}
}
}
}
The implementation is horrible, but we don't have callbacks from Unity side so we need to keep listening that variable value.
Check the documentation:
https://docs.unity3d.com/ScriptReference/iOS.NotificationServices.RegisterForNotifications.html
Also seems to need internet. I guess there is an Apple service going on there.
Yes, registration error is empty even when user deniy permissions.
What i did is to use UniRX and set an Observable fron a Coroutine with a time out so it dont keep forever asking for it.
If you accepted and you do not received the token might be the internet conection. And i guess you are teying this on a real device.
I have not found any official Apple document that discusses correctly implementing push notification simultaneously for old iOS versions, as well as iOS 10. And the independent tutorials I have seen, likewise cover a single iOS version.
I see this official document for iOS 10:
Local and Remote Notifications Overview But it doesn't comment on supporting earlier iOS versions.
And a tutorial for iOS 9:
Push Notifications Tutorial - Ray Wenderlich
And I see various stackoverflow threads about changes people had to make to get their old solutions to work on newer versions:
Push notifications are not working in iOS 9
Which does show code for handling 6 - 9.
didReceiveRemoteNotification not called , iOS 10
BUT what I don't see, is what is correct to do, starting today (with iOS 10), but also supporting older devices. ** UPDATE ** App Store says only 6% of devices downloading apps are older than ios 9, so if it is easier to just support 9 + 10, i'll just do that.
(I tried starting with an iOS 10 example, but it immediately crashed on iOS 9.3 emulated device, though it works fine in iOS 10. So I conclude that I should be starting with information about correctly setting up for the different versions. I could post that code, but I think that leads this thread in the wrong direction. I would rather start with what "should" work on multiple versions of iOS, including 10.)
If I don't find some solution, I'll start combining code from different stackoverflow code snippets ... but seriously? I must be missing something, as presumably every iOS developer has this issue.
Conversely, I could start with an older example, and then follow the changes to get that to work with iOS 10 - but will that take full advantage of iOS 10?
NOTE: I'm programming in Xamarin C#, but Objective-C or Swift answer is just as useful.
This is Xamarin C# code (different syntax and capitalization than Objective-C, but I think it is translatable line-by-line to Objective-C).
Tested on both iOS 9.3 and iOS 10.2.
To initialize "local" and "remote" notifications:
// "UIApplicationDelegate" is for "local" notifications,
// "IUNUserNotificationCenterDelegate, IMessagingDelegate" for "remote" notifications.
public class AppDelegate : UIApplicationDelegate,
IUNUserNotificationCenterDelegate, IMessagingDelegate
{
...
public override bool FinishedLaunching( UIApplication application, NSDictionary launchOptions )
{
...
RegisterForOurRemoteNotifications( this );
RegisterForOurLocalNotifications();
...
}
...
// --- Comment out if not using Google FCM. ---
public override void RegisteredForRemoteNotifications( UIApplication application, NSData deviceToken )
{
//base.RegisteredForRemoteNotifications( application, deviceToken );
Firebase.InstanceID.InstanceId.SharedInstance.SetApnsToken( deviceToken,
Firebase.InstanceID.ApnsTokenType.Sandbox );
}
...
// ----- "static"; Could be in another class. -----
// These flags are for our convenience, so we know initialization was done.
static bool IsRegisteredForNotifications;
static bool IsRegisteredForRemoteNotifications;
// Optional - true when we are using Google "Firebase Cloud Messaging".
static bool HasFCM;
public static void RegisterForOurRemoteNotifications( AppDelegate del )
{
// Google "Firebase Cloud Messaging" (FCM) Monitor token generation
// (Uncomment, if you are using FCM for notifications.)
//InstanceId.Notifications.ObserveTokenRefresh( TokenRefreshNotification );
if (UIDevice.CurrentDevice.CheckSystemVersion( 10, 0 )) {
// iOS 10 or later
var authOptions = UNAuthorizationOptions.Alert | UNAuthorizationOptions.Badge | UNAuthorizationOptions.Sound;
UNUserNotificationCenter.Current.RequestAuthorization( authOptions, ( granted, error ) => {
Console.WriteLine( granted );
} );
// For iOS 10 display notification (sent via APNS)
UNUserNotificationCenter.Current.Delegate = del;
// For iOS 10 data message (sent via Google FCM).
// (Uncomment, if you are using FCM for notifications.)
// TBD: If NOT using FCM, you may need some other lines of code here.
//Messaging.SharedInstance.RemoteMessageDelegate = del;
} else {
// iOS 9 or before
var allNotificationTypes = UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound;
var settings = UIUserNotificationSettings.GetSettingsForTypes( allNotificationTypes, null );
UIApplication.SharedApplication.RegisterUserNotificationSettings( settings );
}
UIApplication.SharedApplication.RegisterForRemoteNotifications();
IsRegisteredForRemoteNotifications = true;
// Uncomment if using Google "Firebase Cloud Messaging" (FCM).
//TokenRefreshNotification( null, null );
//if (UIDevice.CurrentDevice.CheckSystemVersion( 9, 0 )) // Needed to call this twice on iOS 9 for some reason.
// TokenRefreshNotification( null, null );
UIApplication.SharedApplication.SetMinimumBackgroundFetchInterval( UIApplication.BackgroundFetchIntervalMinimum );
}
public static void RegisterForOurLocalNotifications()
{
// --- Our app's notification actions. ---
UNNotificationAction followAction = UNNotificationAction.FromIdentifier( "follow", PS.LocalizedString( "Follow" ), UNNotificationActionOptions.None );
UNNotificationAction likeAction = UNNotificationAction.FromIdentifier( "like", PS.LocalizedString( "Like" ), UNNotificationActionOptions.None );
// ...
// --- Our app's notification categories ---
UNNotificationCategory followCategory = UNNotificationCategory.FromIdentifier( "followCategory", new UNNotificationAction[] { followAction, likeAction },
new string[] { }, UNNotificationCategoryOptions.None );
// ...
// --- All of the app's categories from above ---
var categories = new UNNotificationCategory[] { followCategory /*, ...*/ };
// --- Same for all apps ---
UIUserNotificationSettings settings = UIUserNotificationSettings.GetSettingsForTypes(
UIUserNotificationType.Alert |
UIUserNotificationType.Badge |
UIUserNotificationType.Sound
, new NSSet( categories ) );
UIApplication.SharedApplication.RegisterUserNotificationSettings( settings );
if (UIDevice.CurrentDevice.CheckSystemVersion( 10, 0 )) {
UNUserNotificationCenter.Current.SetNotificationCategories( new NSSet<UNNotificationCategory>( categories ) );
UNUserNotificationCenter.Current.RequestAuthorization( UNAuthorizationOptions.Alert | UNAuthorizationOptions.Sound | UNAuthorizationOptions.Badge,
( result, err ) => {
Console.WriteLine( result.ToString() );
} );
}
IsRegisteredForNotifications = true;
}
}
// -------------------------------------------------------
// --- These are for Google "Firebase Cloud Messaging" ---
// (Comment out if not using FCM.)
public static string Token;
static void TokenRefreshNotification( object sender, NSNotificationEventArgs e )
{
// This method will be fired every time a new token is generated, including the first
// time. So if you need to retrieve the token as soon as it is available this is where that
// should be done.
//var refreshedToken = InstanceId.SharedInstance.Token;
ConnectToFCM( UIApplication.SharedApplication.KeyWindow.RootViewController );
// TODO: If necessary send token to application server.
}
public static void ConnectToFCM( UIViewController fromViewController )
{
Messaging.SharedInstance.Connect( error => {
if (error != null) {
Helper.logD( "Unable to connect to FCM", error.LocalizedDescription );
} else {
//var options = new NSDictionary();
//options.SetValueForKey( DeviceToken, Constants.RegisterAPNSOption );
//options.SetValueForKey( new NSNumber( true ), Constants.APNSServerTypeSandboxOption );
//InstanceId.SharedInstance.GetToken("", InstanceId.ScopeFirebaseMessaging
Token = InstanceId.SharedInstance.Token;
Console.WriteLine( $"Token: {InstanceId.SharedInstance.Token}" );
HasFCM = true;
}
} );
}
// ------------------ End Google FCM ---------------------
// -------------------------------------------------------
}
The above code initializes your app so that it can receive notifications.
IMPORTANT: You also need to set appropriate permissions on your app; see Apple docs, or links mentioned in the question. And you need this file:
Entitlements.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
</dict>
</plist>
<string> above must contain either "development" or "production". (I don't know the significance of our app still saying "development" here; I haven't examined what is built to see if it gets automatically changed to "production" by Xcode before submission to Apple. According to https://stackoverflow.com/a/40857877/199364 it does.)
Then you need code to send [e.g. your app tells your server to notify your friends' devices of what you are doing now] and receive a local or remote notification. That code, in our app, is combined with our specific notification actions and categories; I do not have time to extract a concise version to post here. Please see Apple docs, or links mentioned in the original question, for full details.
Here are the essential methods (Add to class AppDelegate above) to receive notifications:
public override void ReceivedLocalNotification( UIApplication application, UILocalNotification notification )
{
...
}
public override void DidReceiveRemoteNotification( UIApplication application, NSDictionary userInfo, Action<UIBackgroundFetchResult> completionHandler )
{
...
}
[Export( "userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:" )]
public void DidReceiveNotificationResponse( UNUserNotificationCenter center, UNNotificationResponse response, Action completionHandler )
{
...
}
Other methods that you may want/need to override or implement (also see the interfaces declared on class AppDelegate above); some of these might be specific to FCM:
ApplicationReceivedRemoteMessage
ReceivedRemoteNotification
WillPresentNotification
PerformFetch (for background notifications)
HandleAction
I have an iOS application that follows roughly the following steps:
Opens a listening socket.
Accepts a single client connection.
Performs data exchanges to/from client.
When it receives a "resign active" event, it closes and releases all resources associated to the client and server sockets (i.e. invalidates and releases all run loop sources, read/write streams and sockets themselves).
Upon resuming active, it brings back up the listening socket to continue communications (the client will keep trying to reconnect until it is able to, after the iOS app resigned active in step #4).
Whenever a connection does take place between client and server, what I am seeing after step #5 is that the application resumes without being able to reopening the server socket for listening. In other words, even though everything is released in step #5, the application is not able to rebind and listen at the socket address. What's worse, no errors can be detected in the CFSocket API calls while trying to setup the listening socket back again.
If, on the other hand the iOS application resigns active and resumes back again without previously receiving any connection, the client is then able to connect exactly once, until the application resigns and resumes again, in which case the same behaviour above can then be observed.
An example minimal application that illustrates this issue can be found in the following repository:
https://github.com/dpereira/cfsocket_reopen_bug
The most relevant source is:
#import "AppDelegate.h"
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
static void _handleConnect(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void* data, void* info)
{
NSLog(#"Connected ...");
close(*(CFSocketNativeHandle*)data);
NSLog(#"Closed ...");
}
#interface AppDelegate ()
#end
#implementation AppDelegate {
CFRunLoopSourceRef _source;
CFSocketRef _serverSocket;
CFRunLoopRef _socketRunLoop;
}
- (void)applicationWillResignActive:(UIApplication *)application {
CFRunLoopRemoveSource(self->_socketRunLoop, self->_source, kCFRunLoopCommonModes);
CFRunLoopSourceInvalidate(self->_source);
CFRelease(self->_source);
self->_source = nil;
CFSocketInvalidate(self->_serverSocket);
CFRelease(self->_serverSocket);
self->_serverSocket = nil;
CFRunLoopStop(self->_socketRunLoop);
NSLog(#"RELASED SUCCESSFULLY!");
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
CFSocketContext ctx = {0, (__bridge void*)self, NULL, NULL, NULL};
self->_serverSocket = CFSocketCreate(kCFAllocatorDefault,
PF_INET,
SOCK_STREAM,
IPPROTO_TCP,
kCFSocketAcceptCallBack, _handleConnect, &ctx);
NSLog(#"Socket created %u", self->_serverSocket != NULL);
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_len = sizeof(sin);
sin.sin_family = AF_INET;
sin.sin_port = htons(30000);
sin.sin_addr.s_addr= INADDR_ANY;
CFDataRef sincfd = CFDataCreate(kCFAllocatorDefault,
(UInt8 *)&sin,
sizeof(sin));
CFSocketSetAddress(self->_serverSocket, sincfd);
CFRelease(sincfd);
self->_source = CFSocketCreateRunLoopSource(kCFAllocatorDefault,
self->_serverSocket,
0);
NSLog(#"Created source %u", self->_source != NULL);
self->_socketRunLoop = CFRunLoopGetCurrent();
CFRunLoopAddSource(self->_socketRunLoop,
self->_source,
kCFRunLoopCommonModes);
NSLog(#"Registered into run loop");
NSLog(#"Socket is %s", CFSocketIsValid(self->_serverSocket) ? "valid" : "invalid");
NSLog(#"Source is %s", CFRunLoopSourceIsValid(self->_source) ? "valid" : "invalid");
}
#end
The full-blown app resides in: https://github.com/dpereira/conflux
Is there something wrong in the setup/teardown of the sockets (and related resources)?
The issue here is that the listening socket was going into TIME_WAIT and could not be bound to again while in that state.
Even though no errors are returned by the CFSocket API, if the same situation happens when using POSIX sockets an error takes place while trying to re-bind the socket.
The solution was to simply set the SO_REUSEADDR option for the socket just prior to re-binding the socket for listening.
I am trying to setup push notifications using Urban Airship and I believe I'm having a problem with the provisioning profiles or ssl certificates. The app is not prompting the user for Push Notifications permissions but I am not getting a 'no valid "aps-environment" entitlement string found for application' message, and I can see the aps-environment entitlement in the .mobileprovision.
I can't find any documentation on +[UAirship executeUnsafeTakeOff:] so I am wondering if anyone knows what that could mean?
Also, the device token is being returned as nil, as logged by Urban Airship:
[D] -[UAPush updateRegistrationForcefully:] [Line 544] Device token is
nil. Registration will be attempted at a later time
There is no Urban Airship code running prior to the [UAirship takeOff:config] call, and the app does not crash as a result of the error.
Without knowing much about Urban Airship I can offer a guess.
takeOff ensures that the actual implementation provided in executeUnsafeTakeoff only happens once. It makes sure that the current thread is the main thread and then ensures that it only happens once.
Therefore getting an executeUnsafeTakeoff error really only tells you that something went wrong, such as if the configfailed tovalidate` (see below).
You need to make sure that the app can receive push notifications, as you mentioned.
Here is takeOff:
+ (void)takeOff {
[UAirship takeOff:[UAConfig defaultConfig]];
}
+ (void)takeOff:(UAConfig *)config {
// takeOff needs to be run on the main thread
if (![[NSThread currentThread] isMainThread]) {
NSException *mainThreadException = [NSException exceptionWithName:UAirshipTakeOffBackgroundThreadException
reason:#"UAirship takeOff must be called on the main thread."
userInfo:nil];
[mainThreadException raise];
}
dispatch_once(&takeOffPred_, ^{
[UAirship executeUnsafeTakeOff:config];
});
}
Here is executeUnsafeTakeoff:
/*
* This is an unsafe version of takeOff - use takeOff: instead for dispatch_once
*/
+ (void)executeUnsafeTakeOff:(UAConfig *)config {
// Airships only take off once!
if (_sharedAirship) {
return;
}
[UAirship setLogLevel:config.logLevel];
_sharedAirship = [[UAirship alloc] init];
_sharedAirship.config = config;
// Ensure that app credentials have been passed in
if (![config validate]) {
UA_LERR(#"The AirshipConfig.plist file is missing and no application credentials were specified at runtime.");
// Bail now. Don't continue the takeOff sequence.
return;
}
UA_LINFO(#"App Key: %#", _sharedAirship.config.appKey);
UA_LINFO(#"App Secret: %#", _sharedAirship.config.appSecret);
UA_LINFO(#"Server: %#", _sharedAirship.config.deviceAPIURL);
if (config.automaticSetupEnabled) {
_sharedAirship.appDelegate = [[UAAppDelegateProxy alloc ]init];
//swap pointers with the initial app delegate
#synchronized ([UIApplication sharedApplication]) {
_sharedAirship.appDelegate.originalAppDelegate = [UIApplication sharedApplication].delegate;
_sharedAirship.appDelegate.airshipAppDelegate = [[UAAppDelegate alloc] init];
[UIApplication sharedApplication].delegate = _sharedAirship.appDelegate;
}
}
// Build a custom user agent with the app key and name
[_sharedAirship configureUserAgent];
// Set up analytics
_sharedAirship.analytics = [[UAAnalytics alloc] initWithConfig:_sharedAirship.config];
[_sharedAirship.analytics delayNextSend:UAAnalyticsFirstBatchUploadInterval];
/*
* Handle Debug Options
*/
//For testing, set this value in AirshipConfig to clear out
//the keychain credentials, as they will otherwise be persisted
//even when the application is uninstalled.
if (config.clearKeychain) {
UA_LDEBUG(#"Deleting the keychain credentials");
[UAKeychainUtils deleteKeychainValue:_sharedAirship.config.appKey];
UA_LDEBUG(#"Deleting the UA device ID");
[UAKeychainUtils deleteKeychainValue:kUAKeychainDeviceIDKey];
}
if (!config.inProduction) {
[_sharedAirship validate];
}
if (config.cacheDiskSizeInMB > 0) {
UA_LINFO("Registering UAURLProtocol");
[NSURLProtocol registerClass:[UAURLProtocol class]];
}
// The singleton is now ready for use!
_sharedAirship.ready = true;
//create/setup user (begin listening for device token changes)
[[UAUser defaultUser] initializeUser];
}