• Breaking Chat Heads Out of the iOS Sandbox

    Tuesday, Jan 14, 2014

    Background Info

    On April 12th, 2013, Facebook released Facebook Home, a completely new mobile experience available solely to Android users. With Home came Chat Heads, a wickedly cool way to chat with your friends, in a way that makes that “face-to-face conversation experience” a little more feasible with text messaging. The minute I saw it, I thought “I can make this!”.

    Using box2d as a boilerplate, I started working with giving contact photos the same (or similar anyways) physics that powers Chat Heads. I got some progress, and made it system-wide, but it wasn’t even close to being complete. It wasn’t but 2-3 days later that Chat Heads was announced for iOS. However, due to Apple’s sandboxing and policies, it was solely contained in the Facebook app :(

    box2d

    STOP. APP STORE. INSTALL... was my first thought.

    STOP. HOLD THE PHONE. HAX... was my second.

    Why not “break” Chat Heads out of the iOS Facebook app?

    XPC and Watchdog

    The Facebook app is pretty complex, loads of view controllers and modules to make it all work together, but thankfully Chat Heads has its own view controller, and with that a view! Well that’s fantastic and all, you can’t just start ripping views out of an app and shoving it somewhere else, or so I thought...

    I thought of using XPC to fix this. XPC is a framework developed by Apple that allows you to exchange things between running processes or applications. Those things are somewhat limited:

    1. XPC only plays nice with serializable objects (aka Dictionaries, Strings, and stuff)
    2. Apple’s doing cooler things with views!

    When you open the multitasking bar, by double clicking the home button (or using that awesome slide-up gesture provided by Zephyr) Apple needed a way to move the current app upwards, or transform how it looks, without changing how the actual application renders. They didn’t want to mess with any custom code in the application, so they created another context to share the application’s rendering view (if that makes sense). SBAppContextHostManager handles all this. An application on iOS runs inside a UIWindow, a window that holds a bunch of views. So if we copy all the views inside that UIWindow, and render them inside another view, you can do whatever you want to this new view, without altering the original. Think of it as taking a photo, duplicating it, and messing with the duplicate. You can mess with it all you want, but the original stays the same. When you open the multitasking bar, Apple uses SBAppContextHostManager to create a new view and move it upwards to show the multitasking bar underneath, that way the original application’s layout isn’t touched.

    I could do that with the Facebook app; render the Facebook app inside another view, and put it somewhere where it’s on top of everything else. This worked perfectly until you actually closed the Facebook app.

    To save power, when an application is closed, the views aren’t rendered anymore (so the GPU can rest), unless you tell it explicitly not to! That's where some private API comes into play!

    +[UIWindow setAllWindowsKeepContextInBackground] 
    

    ... until a few minutes later when iOS decides to kill the Facebook app. :-|

    This was the original problem shown in the Verge article; it freezes. On iOS there’s a daemon called watchdog. This daemon watches over everything running on iOS, and if you’re taking up too much memory or are in the background for too long, it’ll kill your app. It’s fantastic to save power and keep things running quickly, but annoying in this instance.

    Some applications, however, can be granted permission to continue to run in the background, for example Reprise. Reprise is a music player, and if you give it backgrounding permissions, it’ll continue to play music after you close the app. Unfortunately Facebook isn’t a music player, and doesn’t have any special backgrounding permissions (other than for VOIP purposes), so I had to tweak things on a lower level than just the Facebook app.

    BackBoardServices

    On iOS the main homescreen / lockscreen is called SpringBoard, but in iOS 6, Apple made things more interesting and decided to split up some of SpringBoard into backboardd. The result? SpringBoard handles more springing, and backboardd handles more of the behind-the-scenes application life managing. There’s a neat class in BackBoardServices (a private framework) called BKSProcessAssertion. This class allows you to give a certain application permissions to stay alive and not be killed by watchdog. So then I basically gave the Facebook app infinite backgrounding using the appropriate flags. To do this, I granted SpringBoard the ability to hand out BKSProcessAssertions to running applications (makes things easier tweak-wise). Yay! No more pausing, no more getting killed, and it’s system-wide!

    // Replace this function using MobileSubstrate with a dylib into backboardd (or install MessageBox)
    static int hax_XPConnectionHasEntitlement(id connection, NSString *entitlement)
    {
      //Only grant the required entitlement
      if (xpc_connection_get_pid(connection) == PIDForProcessNamed(@"SpringBoard") && [entitlement isEqualToString:@"com.apple.multitasking.unlimitedassertions"])
      return 1;
    
      return orig_XPConnectionHasEntitlement(connection, entitlement);
    }
    

    Cycript to the Rescue!

    Cycript is awesome. It's basically a prompt that can hook into processes and accepts javascript as well as Objective-C syntax to play with memory. It requires jailbreaking your device, but @saurik has also released a version that you can link with App Store applications for debugging :D It's perfect for prototyping, and this was how I created the first prototype that was shown on the Verge. The best part is if anything goes wrong while messing in Cycript, you can just restart the process you're hooking, therefore doing no permanent damage (hopefully! :P).

    Hosting Views Where They Probably Shouldn't Be

    By creating a SBContextHostView for the Facebook app, one can display the whole Facebook app on top of any running application. You'll also need a BKSProcessAssertion (as mentioned earlier) to keep it running.

    SBApplication *app = [[SBApplicationController sharedInstance] applicationWithDisplayIdentifier:@"com.facebook.Facebook"];
    UIView *hostView = [app1 contextHostViewForRequester:@"epichax1" enableAndOrderFront:YES];
    [[UIApplication keyWindow] addSubview:view1];
    view1.transform = CGAffineTransformMakeScale(0.4, 0.4);
    view1.frame = CGRectMake(0, 0, 320, 568);
    
    BKSProcessAssertion *keepAlive = [[BKSProcessAssertion alloc] initWithPID:[app pid]
                                                                        flags:0xF
                                                                       reason:7
                                                                         name:@"epichax1"
                                                                  withHandler:nil];
    

    Tada! Mini Facebook app! :D

    For JailbreakCon 2013 I wrote a cycript demo that does all this and displays 4 applications running simultaneously on top of SpringBoard. It's hilarious to see!

    ALLTheApps

    Post-Cycript

    Of course there was much more work than this to actually make it usable and pleasant, such as transitions when quitting the Facebook app, and handling rotation even when the Facebook app is closed. Originally I was refreshing the app every 15-60 seconds to see if there were any new chat messages, however this shoved battery life into the ground and stomped on it. To save battery life, I hooked Facebook Messenger’s push notifications and redirected them to the Facebook app so Chat Heads update their conversations accordingly.

    When all was said and done I released this hack, for free, under the name MessageBox. Sadly this no longer works on iOS 7 (nor the current version of the Facebook app), but I'll be sure to bring it back... someday!

    Facebook Chat Heads are a wonderful way to chat. The ability to be able to chat with anyone, from inside any application, without the need to switch apps is something that isn’t new to mobile platforms (Android for example), however Chat Heads takes things to the next level. An experience that still, to this day, makes me smile when a chat head springs off one side and docks on the other.