Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Behind the curtains: how Photos™ work

Behind the curtains: how Photos™ work

Google Photos is an amazing piece of software, and API are pretty amazing. The only problem? They are not available on Android just yet. Join us to learn how we bypassed this limitation to create a selfie box based on Kotlin, Things™, and, of course, Google Photos!


Roberto Orgiu

October 06, 2018

More Decks by Roberto Orgiu

Other Decks in Programming


  1. Behind the curtains: HOW PHOTOS™ WORKS

  2. HELLO! I am Robertoooooooo I do things Android at The

    New York Times You can find me at @_tiwiz
  3. sli.do #2170 ask your questions during the presentation

  4. We ♡ github.com/danybony/do-it-yourselfie

  5. THE FLOW®

  6. None
  7. LOGIN • No touchscreen • No users • No screen

  8. COMPANION APP Not such a great idea, right?

  9. Place your screenshot here ANDROID COMPANION APP Lets you pick

    a user, authenticate and send the token to the Android Things™ app.
  10. private val oAuthSecret = "S3cR3t11!" private val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)

    .requestEmail() .requestScopes(Scope(PHOTOS_SHARING_SCOPE), Scope(PHOTOS_READ_APPEND_SCOPE)) .requestIdToken(oAuthSecret) .build() private val client = GoogleSignIn.getClient(host, gso)
  11. host.startActivityForResult( client.signInIntent, AUTHENTICATION_CODE )

  12. val task = GoogleSignIn.getSignedInAccountFromIntent(intent) val account = task.getResult(ApiException::class.java) dispatchToken(account)

  13. val credential = GoogleAccountCredential.usingOAuth2(host, scopes).apply { selectedAccount = account.account }

    launch(UI) { val token = async { credential.token } host.showLoggedUi(token.await()) }
  14. None

  16. class TokenMessageReceiver : MessageListener() { override fun onFound(message: Message) {

    onTokenReceived(String(message.content)) } }
  17. fun onActivityStart() { Nearby.getMessagesClient(activity) .subscribe(tokenMessageReceiver) } fun onActivityStop() { Nearby.getMessagesClient(activity)

    .unsubscribe(tokenMessageReceiver) }
  18. val message = Message(token.toByteArray())

  19. Nearby.getMessagesClient(activity) .publish(message) Nearby.getMessagesClient(activity) .unpublish(message)

  20. None
  21. @GET("albums") fun fetchAlbums(@Header("Authorization") token: String, @Query("pageToken") nextPageToken: String? = null

    ): Deferred<AlbumListResponse>
  22. data class AlbumListResponse( val albums: Array<CompleteAlbum>, val nextPageToken: String? )

    data class AlbumResponse( val productUrl: String, val id: String, val title: String, @Json(name = "isWriteable") val writeable: Boolean )
  23. internal fun fetchAlbums(): Deferred<ArrayList<CompleteAlbum>> { return async { val albums

    = arrayListOf<CompleteAlbum>() var nextPageToken: String? = null do { val page = apiService .fetchAlbums(tokenBearer, nextPageToken).await() nextPageToken = nextPageToken .replaceWith(page.nextPageToken) albums.addAll(page.albums) } while (nextPageToken != null && nextPageToken.isNotEmpty()) return@async albums } }
  24. fun String?.replaceWith(nextPageToken: String?): String? = if (this != nextPageToken) {

    nextPageToken } else { null }
  25. internal fun uploadImage(albumId: String, fileName: String, bytes: ByteArray): Deferred<ImageUploadResult> {

    return async { val token = uploadApiService .uploadMedia(tokenBearer, fileName, bytes).await() apiService .createMediaLink(tokenBearer, token.asImageUploadRequestWith(albumId, fileName) ).await() } }
  26. fun uploadMedia(token: String, fileName: String, bytes: ByteArray): Deferred<String> { val

    requestBody = RequestBody.create(MediaType.parse(OCTET_TYPE), bytes) return async { val request = Request.Builder() .url(endpoint) .addHeader(AUTHORIZATION, token) .addHeader(UPLOAD_FILENAME, fileName) .post(requestBody) .build() client.newCall(request).execute().body().string() } }
  27. @POST("./mediaItems:batchCreate") fun createMediaLink( @Header("Authorization") token: String, @Body request: ImageUploadRequest ):

  28. An album only shows in Photos™ when it has at

    least one picture. Cannot delete a picture. You can only interact with albums you created. LESSONS LEARNED

  30. AVAILABLE NOW API EXAMPLES LIBRARIES developers.google.com/photos

  31. AVAILABLE NOW API EXAMPLES LIBRARIES bit.ly/photos-java-client

  32. AVAILABLE NOW API EXAMPLES LIBRARIES github.com/google/java-photoslibrary

  33. JOIN US androiddevs.it

  34. THANKS! Questions time! You can find me at @_tiwiz