Thursday, 25 September 2014

Upgrading to Google Cast iOS SDK 2.4.0

Recently I've been working on the samples for the Chromecast SDK on iOS. Since we've just updated the sample app for the brand new 2.4.0 SDK, I thought I'd briefly note down the changes I had to make, which should help if you need to update your own app from 2.3.0. As always, the full list of changes can be found in the release notes.

Update Frameworks

The first step is to drop the new GoogleCast.framework into the project. You'll immediately notice that some classes have changed, and that there is a new dependency on SystemConfiguration.framework, so add that in your Linked Frameworks and Libraries section in the General Project Settings.

GCKDeviceScanner

To simplify the scanning and connection process, filtering has been merged right into GCKDeviceScanner. Start by removing any references to GCKDeviceFilterListener or GCKDeviceFilter as this functionality is now part of the GCKDeviceScanner directly. Remove any of the delegate methods you may have implemented in GCKDeviceFilterListener, but keep the contents - you can use the same in the GCKDeviceScannerListener methods.

Remove any references to add/removeDeviceFilterListener, then move the contents of: deviceDidComeOnline:forDeviceFilter: into the GCKDeviceScannerListener method: deviceDidComeOnline:.

For deviceDidGoOffline:forDeviceFilter:, use the GCKDeviceScannerListener equivalent: deviceDidGoOffline:.

Where you would have initiated scanning by creating a GCKDeviceFilter with criteria and a scanner, you now attach the filter criteria directly to the GCKDeviceScanner. Keep your GCKFilterCriteria object, and instead of calling initWithDeviceScanner:criteria: simply attach your criteria object to the filterCriteria property on your GCKDeviceScanner: self.deviceScanner.filterCriteria = filterCriteria;

Listing Devices

Where you would have iterated over the GCKDeviceFilter.devices array to get the currently discovered devices, you can now use the GCKDeviceScanner.devices array directly. Replace all references to the filter array with the GCKDeviceScanner devices one.

There is a gotcha here to be aware of though! The devices array is updated after the deviceDidGoOffline call is fired. If you're using that callback to update whether or not the Cast icon appears in your app, you might hit trouble. The solution is to do your UI update in the next runloop, which you can easily do with performSelector. In the CastVideos sample, we do exactly that:

GCKDeviceManager

The other class that has received significant attention is GCKDeviceManager, with new properties and callbacks to give you more information, or easier access to that information. Some of these will require changes in your code though.

The GCKDeviceManagerDelegate method deviceManager:didReceiveStatusForApplication: has a new name. If you implemented this method, update it to the new signature: deviceManager:didReceiveApplicationMetadata:.

If you were listening for deviceManager:didDisconnectWithError to pick up network based disconnections (e.g. the WiFi disappearing), there is a small issue that that this wont fire if you disable the WiFi on your device, which you may do when testing or similar). However, you can listen to the new deviceManager:didSuspendConnectionWithReason: and will get a callback with the reason GCKConnectionSuspendReasonNetworkError (see Connection Suspension below) in any case.

Error Handling

One wider change is that many GCKDeviceManager methods now return an NSInteger containing the request ID rather than a BOOL, with immediate failure indicated by the value kInvalidRequestID, which conveniently is 0. This allows tracking whether requests subsequently fail, thanks to a new delegate protocol method: deviceManager:request:didFailWithError:.

Connection Suspension

There are new delegate methods as well for suspended and resumed connections, didSuspendConnectionWithReason and deviceManagerDidResumeConnection:rejoinedApplication. Suspended indicates the connection has been interrupted, but the device manager will attempt to connect automatically. You shouldn’t use this method to force a reconnection, but you might want to hint in the UI that the Chromecast device isn’t going to respond immediately to commands. GCKConnectionSuspendedReason is an enum indicating whether the suspension is due to the app being backgrounded, or a network related problem.

Device Status Text

The device manager protocol also has new status information callbacks. One of the most useful is a text field describing the currently running application: deviceManager:didReceiveApplicationStatusText:. This will contain "YouTube TV" or similar to indicate the app currently running. This can be helpful to display to users in case there are similarly named Cast devices on their network. The same information is conveniently available on a GCKDevice directly as the statusText property.

Launching An App

Finally, if you were previously using launchApplication:relaunchIfRunning: then its worth noting that method has been deprecated in favour of a more extensible launchApplication:withLaunchOptions. The second parameter is a new GCKLaunchOptions object. To recreate the old call, you can use:

Monday, 4 August 2014

Detect whether users have uploaded a profile picture to Google+

Very short post on a small feature that I know will be a popular one with some people! A regular feature request has been the ability to determine whether the profile picture with a Google+ profile is the default blue head or not. You can do that right now with the people.get API calls - and you can try it yourself from that page using the explorer.

The change is that under the "image" key you'll see an "isDefault" value. For my user you can see it's false:

"image": {
  "url": "https://lh6.googleusercontent.com/.../photo.jpg?sz=50",
  "isDefault": false
 },

But for this blue head user, it's true:

"image": {
  "url": "https://lh3.googleusercontent.com/.../photo.jpg?sz=50",
  "isDefault": true
 },

Hopefully this should make it easier to determine whether to use the profile picture from Google+ when people sign in to your apps.Client libraries might take some time to be regenerated, but its certainly available in the API right now.

Wednesday, 18 June 2014

Google Sign In with Server Side Auth on iOS

The release today of version 1.7 of the Google+ iOS SDK added the ability to authorise both a client and server for access to Google APIs. This has been a feature for Android and Web based sign-ins for a while, and now is available across all three platforms. This should simplify server side code for people who have been building cross platform apps - for example if you need to retrieve profile information on the client, but retrieve circles on a server for a friend finding feature.

The implementation for iOS is fairly straightforward. When setting up authentication, simply specify the client ID of your server on on the GPPSignIn object. This should be a client ID from the developer console which is used by your server-side code, and should be part of the same console project as your iOS client ID. For those that have done it, this is the same parameter used when retrieving an ID token:

You can see that the first part of the client ID (the project number) is the same, but the other part varies. If you try and exchange a code generated for one client ID using a different client ID you'll get an invalid_client error, even if both client IDs are part of the same project. When the user is signed in via [GPPSignIn authenticate] you can retrieve the code parameter from the GPPSignIn instance as part of the auth callback:

You can send this parameter to your server exactly as you would with one from Javascript or Android. When exchanging, be sure to not set a redirect uri in the server side auth configuration or you will get back a HTTP 400 error "redirect_uri_mismatch" from the token POST call. The exchange should always result in a refresh token (unlike with the web), so be sure to store that safely in a database or other persistent store if you're using it.

If the user is seamlessly signed in via a call to trySilentAuthentication, you won’t receive a code to use. Instead of forcing the user to sign in again with authenticate (and swap out to the browser or Google+ app to consent again) when you’re establishing a session with the server, try sending across an ID token and checking whether the server already has a refresh token for that user - this will mean a smoother experience for the user in most cases.

Thursday, 6 March 2014

Migrating Away From Userinfo

As part of the move to full OpenID connect support recently, the "userinfo" scopes and endpoint were deprecated and scheduled for shutdown in September 2014. If you are using the userinfo API endpoint to retrieve email address or profile information for a Google user, now is the time to change! Luckily, it's just a few minutes of work to move from the userinfo API to the people.get API for most people, and wont affect users at all.

What should you do?

  1. Check whether you're using userinfo
  2. Update scopes
  3. Replace userinfo API call with a call to plus.people.get

Are you using the endpoint?

Look for the section in your code where you retrieve the user's profile or email address. If you make the API call directly, you may see a URL like "https://www.googleapis.com/oauth2/v1/userinfo". The "v1" might also be "v2" as there are a couple of different versions of the api, but if it has /oauth2 then you're using the userinfo endpoint. If you're using a client library, look for using the (somewhat confusingly named) OAuth2 service or API. For example:

PHP: new Google_Service_Oauth2($client);
Python: service = build('oauth2', 'v2', http=http)
Java: oauth2 = new Oauth2.Builder(httpTransport, JSON_FACTORY, credential).build();

All of these are indicative of retrieving the user data from the userinfo API, and will need to be changed before September 2014.

What scopes should you use?

https://www.googleapis.com/auth/userinfo.profile and https://www.googleapis.com/auth/userinfo.email can be seamlessly replaced with the shorter strings profile and email. These are aliases, so they wont require the user to reconsent if they've already give access. If you're thinking about a larger change, check out my earlier post about choosing a sign in scope.

What API should you call?

All profile information for Google users is now served from the people.get API. This works for users whether or not they have a Google+ profile. It returns data in a slightly different format to the userinfo API, so you may have to change your code a little, but all the same data is available. A call to retrieve the users name, picture and email address would look like this in PHP (and should be analogous with any of the client libraries).

You can actually try the call direct from the "Try It" section at the bottom of the API page to see what the returned data looks like.

What if you want to make the smallest code change possible?

If you aren't using a client library, and don't want to change the parsing you're using, you can call the special getOpenIdConnect method, which returns data in the same format as userinfo. The only difference needed then is looking for the "sub" field rather than "id" (this was a spec change made during the development of OpenID Connect). Take a look at the differences between the calls to the API below

Userinfo API response

https://www.googleapis.com/oauth2/v1/userinfo?access_token=ya29.1.AA...
{
 "id": "104824858261236811362",
 "email": "ian@example.com",
 "verified_email": true,
 "name": "Ian Barber",
 "given_name": "Ian",
 "family_name": "Barber",
 "link": "https://plus.google.com/+IanBarber",
 "picture": "https://lh6.googleusercontent.com/-DXmOngLN6Gc/AAAAAAAAAAI/AAAAAAAAGc4/Roeci0EovY8/photo.jpg",
 "locale": "en-GB"
}
openIdConnect API response:
https://www.googleapis.com/plus/v1/people/me/openIdConnect?access_token=ya29.1.AA>...
{
 "kind": "plus#personOpenIdConnect",
 "sub": "104824858261236811362",
 "name": "Ian Barber",
 "given_name": "Ian",
 "family_name": "Barber",
 "profile": "https://plus.google.com/+IanBarber",
 "picture": "https:https://lh6.googleusercontent.com/-DXmOngLN6Gc/AAAAAAAAAAI/AAAAAAAAGc4/Roeci0EovY8/photo.jpg?sz=50",
 "email": "ian@example.com",
 "email_verified": "true",
 "locale": "en-GB"
}

In both cases, you do get a fair bit less than using the people.get API, so I would strongly recommend using people.get where possible!

There are a lot more tips on migrating to the newer sign-in options on the auth migration page, and of course if you have any questions drop into the StackOverflow tags for google-plus and google-oauth.