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

Seamless Linking to Your App

Seamless Linking to Your App

An Android app is usually only a single part of a larger product. Indeed, a product is usually made of several independent entities such as a website, one or several mobile apps, etc.

In this talk, we will learn how to increase app engagement and tear down the walls between your website and your apps. You will also discover how you can give your users the most integrated mobile experience possible with features such as Related Apps Banner, Smart Lock for Passwords and more… In a nutshell, this talk is all about driving users to your mobile app and making your product successful.

Cyril Mottier

November 10, 2016
Tweet

More Decks by Cyril Mottier

Other Decks in Programming

Transcript

  1. <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" />

    <data android:scheme="https" android:host="www.trainline.eu" /> <data android:path="/search" /> <data android:pathPrefix="/search/" /> </intent-filter>
  2. <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" />

    <data android:scheme="https" android:host="www.trainline.eu" /> <data android:path="/search" /> <data android:pathPrefix="/search/" /> </intent-filter> <action android:name="android.intent.action.VIEW" />
  3. <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" />

    <data android:scheme="https" android:host="www.trainline.eu" /> <data android:path="/search" /> <data android:pathPrefix="/search/" /> </intent-filter> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" />
  4. <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" />

    <data android:scheme="https" android:host="www.trainline.eu" /> <data android:path="/search" /> <data android:pathPrefix="/search/" /> </intent-filter> <data android:scheme="https" android:host="www.trainline.eu" />
  5. <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" />

    <data android:scheme="https" android:host="www.trainline.eu" /> <data android:path="/search" /> <data android:pathPrefix="/search/" /> </intent-filter> <data android:path="/search" /> <data android:pathPrefix="/search/" />
  6. <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" />

    <data android:scheme="https" android:host="www.trainline.eu" /> <data android:path="/search" /> <data android:pathPrefix="/search/" /> </intent-filter> Are you f***ing serious? The real world is that simple?
  7. <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" />

    <data android:scheme="https" android:host="www.trainline.eu" /> <data android:path="/search" /> <data android:pathPrefix="/search/" /> </intent-filter> Nope…
  8. <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" />

    <data android:scheme="http" /> <data android:scheme="https" /> <data android:host="captaintrain.com" /> <data android:host="trainline.de" /> <data android:host="trainline.es" /> <data android:host="trainline.eu" /> <data android:host="trainline.fr" /> <data android:host="trainline.it" /> <data android:host="www.captaintrain.com" /> <data android:host="www.trainline.de" /> <data android:host="www.trainline.es" /> <data android:host="www.trainline.eu" /> <data android:host="www.trainline.fr" /> <data android:host="www.trainline.it" /> <data android:path="/search" /> <data android:pathPrefix="/search/" /> </intent-filter>
  9. <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" />

    <data android:scheme="http" /> <data android:scheme="https" /> <data android:host="captaintrain.com" /> <data android:host="trainline.de" /> <data android:host="trainline.es" /> <data android:host="trainline.eu" /> <data android:host="trainline.fr" /> <data android:host="trainline.it" /> <data android:host="www.captaintrain.com" /> <data android:host="www.trainline.de" /> <data android:host="www.trainline.es" /> <data android:host="www.trainline.eu" /> <data android:host="www.trainline.fr" /> <data android:host="www.trainline.it" /> <data android:path="/search" /> <data android:pathPrefix="/search/" /> </intent-filter> <data android:host="www.trainline.de" /> <data android:host="www.trainline.es" /> <data android:host="www.trainline.eu" /> <data android:host="www.trainline.fr" /> <data android:host="www.trainline.it" /> <data android:scheme="https" />
  10. <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" />

    <data android:scheme="http" /> <data android:scheme="https" /> <data android:host="captaintrain.com" /> <data android:host="trainline.de" /> <data android:host="trainline.es" /> <data android:host="trainline.eu" /> <data android:host="trainline.fr" /> <data android:host="trainline.it" /> <data android:host="www.captaintrain.com" /> <data android:host="www.trainline.de" /> <data android:host="www.trainline.es" /> <data android:host="www.trainline.eu" /> <data android:host="www.trainline.fr" /> <data android:host="www.trainline.it" /> <data android:path="/search" /> <data android:pathPrefix="/search/" /> </intent-filter> <data android:scheme="http" /> <data android:host="captaintrain.com" /> <data android:host="trainline.de" /> <data android:host="trainline.es" /> <data android:host="trainline.eu" /> <data android:host="trainline.fr" /> <data android:host="trainline.it" /> <data android:host="www.captaintrain.com" />
  11. public final class AppConfig { private AppConfig() { } public

    static final List<String> SCHEMES = Collections.unmodifiableList(Arrays.asList( "http", "https")); public static final List<String> AUTHORITIES = Collections.unmodifiableList(Arrays.asList( "captaintrain.com", "trainline.de", "trainline.es", "trainline.eu", "trainline.fr", "trainline.it", "www.captaintrain.com", "www.trainline.de", "www.trainline.es", "www.trainline.eu", "www.trainline.fr", "www.trainline.it")); public static final String PATH_SEARCH = "search"; }
  12. public final class SearchActivity extends Activity { @Override protected void

    onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); String departure = null; String arrival = null; final Uri uri = getIntent().getData(); if (uri != null) { if (AppConfig.AUTHORITIES.contains(uri.getAuthority()) && AppConfig.SCHEMES.contains(uri.getScheme())) { final List<String> segments = uri.getPathSegments(); if (segments != null && segments.size() == 3 && AppConfig.PATH_SEARCH.equals(segments.get(0))) { arrival = segments.get(1); departure = segments.get(2); } } } startSearch(departure, arrival); } }
  13. public final class SearchActivity extends Activity { @Override protected void

    onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); String departure = null; String arrival = null; final Uri uri = getIntent().getData(); if (uri != null) { if (AppConfig.AUTHORITIES.contains(uri.getAuthority()) && AppConfig.SCHEMES.contains(uri.getScheme())) { final List<String> segments = uri.getPathSegments(); if (segments != null && segments.size() == 3 && AppConfig.PATH_SEARCH.equals(segments.get(0))) { arrival = segments.get(1); departure = segments.get(2); } } } startSearch(departure, arrival); } } if (AppConfig.AUTHORITIES.contains(uri.getAuthority()) && AppConfig.SCHEMES.contains(uri.getScheme())) {
  14. public final class SearchActivity extends Activity { @Override protected void

    onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); String departure = null; String arrival = null; final Uri uri = getIntent().getData(); if (uri != null) { if (AppConfig.AUTHORITIES.contains(uri.getAuthority()) && AppConfig.SCHEMES.contains(uri.getScheme())) { final List<String> segments = uri.getPathSegments(); if (segments != null && segments.size() == 3 && AppConfig.PATH_SEARCH.equals(segments.get(0))) { arrival = segments.get(1); departure = segments.get(2); } } } startSearch(departure, arrival); } } final List<String> segments = uri.getPathSegments(); if (segments != null && segments.size() == 3 && AppConfig.PATH_SEARCH.equals(segments.get(0))) { arrival = segments.get(1); departure = segments.get(2); }
  15. public final class SearchActivity extends Activity { @Override protected void

    onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); String departure = null; String arrival = null; final Uri uri = getIntent().getData(); if (uri != null) { if (AppConfig.AUTHORITIES.contains(uri.getAuthority()) && AppConfig.SCHEMES.contains(uri.getScheme())) { final List<String> segments = uri.getPathSegments(); if (segments != null && segments.size() == 3 && AppConfig.PATH_SEARCH.equals(segments.get(0))) { arrival = segments.get(1); departure = segments.get(2); } } } startSearch(departure, arrival); } } startSearch(departure, arrival);
  16. Available on Marshmallow+ (API 23) Consider app & website as

    a single entity Prevent “Open with” dialog
  17. Available on Marshmallow+ (API 23) Consider app & website as

    a single entity Prevent “Open with” dialog
  18. Available on Marshmallow+ (API 23) Consider app & website as

    a single entity Prevent “Open with” dialog
  19. <activity ...> <intent-filter android:autoVerify="true"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" />

    <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" /> <data android:scheme="https" /> <data android:host="captaintrain.com" /> <data android:host="trainline.de" /> <data android:host="trainline.es" /> <data android:host="trainline.eu" /> <data android:host="trainline.fr" /> <data android:host="trainline.it" /> <data android:host="www.captaintrain.com" /> <data android:host="www.trainline.de" /> <data android:host="www.trainline.es" /> <data android:host="www.trainline.eu" /> <data android:host="www.trainline.fr" /> <data android:host="www.trainline.it" /> <data android:path="/search" /> <data android:pathPrefix="/search/" /> </intent-filter> </activity>
  20. <activity ...> <intent-filter android:autoVerify="true"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" />

    <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" /> <data android:scheme="https" /> <data android:host="captaintrain.com" /> <data android:host="trainline.de" /> <data android:host="trainline.es" /> <data android:host="trainline.eu" /> <data android:host="trainline.fr" /> <data android:host="trainline.it" /> <data android:host="www.captaintrain.com" /> <data android:host="www.trainline.de" /> <data android:host="www.trainline.es" /> <data android:host="www.trainline.eu" /> <data android:host="www.trainline.fr" /> <data android:host="www.trainline.it" /> <data android:path="/search" /> <data android:pathPrefix="/search/" /> </intent-filter> </activity> <action android:name="android.intent.action.VIEW" />
  21. <activity ...> <intent-filter android:autoVerify="true"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" />

    <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" /> <data android:scheme="https" /> <data android:host="captaintrain.com" /> <data android:host="trainline.de" /> <data android:host="trainline.es" /> <data android:host="trainline.eu" /> <data android:host="trainline.fr" /> <data android:host="trainline.it" /> <data android:host="www.captaintrain.com" /> <data android:host="www.trainline.de" /> <data android:host="www.trainline.es" /> <data android:host="www.trainline.eu" /> <data android:host="www.trainline.fr" /> <data android:host="www.trainline.it" /> <data android:path="/search" /> <data android:pathPrefix="/search/" /> </intent-filter> </activity> <category android:name="android.intent.category.BROWSABLE" />
  22. <activity ...> <intent-filter android:autoVerify="true"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" />

    <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" /> <data android:scheme="https" /> <data android:host="captaintrain.com" /> <data android:host="trainline.de" /> <data android:host="trainline.es" /> <data android:host="trainline.eu" /> <data android:host="trainline.fr" /> <data android:host="trainline.it" /> <data android:host="www.captaintrain.com" /> <data android:host="www.trainline.de" /> <data android:host="www.trainline.es" /> <data android:host="www.trainline.eu" /> <data android:host="www.trainline.fr" /> <data android:host="www.trainline.it" /> <data android:path="/search" /> <data android:pathPrefix="/search/" /> </intent-filter> </activity> <data android:scheme="http" /> <data android:scheme="https" />
  23. <activity ...> <intent-filter android:autoVerify="true"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" />

    <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" /> <data android:scheme="https" /> <data android:host="captaintrain.com" /> <data android:host="trainline.de" /> <data android:host="trainline.es" /> <data android:host="trainline.eu" /> <data android:host="trainline.fr" /> <data android:host="trainline.it" /> <data android:host="www.captaintrain.com" /> <data android:host="www.trainline.de" /> <data android:host="www.trainline.es" /> <data android:host="www.trainline.eu" /> <data android:host="www.trainline.fr" /> <data android:host="www.trainline.it" /> <data android:path="/search" /> <data android:pathPrefix="/search/" /> </intent-filter> </activity> android:autoVerify="true"
  24. [ { "relation": [ "delegate_permission/common.handle_all_urls" ], "target": { "namespace": "android_app",

    "package_name": "com.capitainetrain.android", "sha256_cert_fingerprints": [ “5A:BF:2C:43:1A:2E:54:A3:60:31:58:A7:62:AA:4D:E0:AA:BE: 50:F7:00:36:3C:CB:41:CD:83:FE:F5:B9:58:2C” ] } } ]
  25. [ { "relation": [ "delegate_permission/common.handle_all_urls" ], "target": { "namespace": "android_app",

    "package_name": "com.capitainetrain.android", "sha256_cert_fingerprints": [ “5A:BF:2C:43:1A:2E:54:A3:60:31:58:A7:62:AA:4D:E0:AA:BE: 50:F7:00:36:3C:CB:41:CD:83:FE:F5:B9:58:2C” ] } } ] "relation": [ "delegate_permission/common.handle_all_urls" ],
  26. [ { "relation": [ "delegate_permission/common.handle_all_urls" ], "target": { "namespace": "android_app",

    "package_name": "com.capitainetrain.android", "sha256_cert_fingerprints": [ “5A:BF:2C:43:1A:2E:54:A3:60:31:58:A7:62:AA:4D:E0:AA:BE: 50:F7:00:36:3C:CB:41:CD:83:FE:F5:B9:58:2C” ] } } ] "namespace": "android_app",
  27. [ { "relation": [ "delegate_permission/common.handle_all_urls" ], "target": { "namespace": "android_app",

    "package_name": "com.capitainetrain.android", "sha256_cert_fingerprints": [ “5A:BF:2C:43:1A:2E:54:A3:60:31:58:A7:62:AA:4D:E0:AA:BE: 50:F7:00:36:3C:CB:41:CD:83:FE:F5:B9:58:2C” ] } } ] "package_name": "com.capitainetrain.android",
  28. [ { "relation": [ "delegate_permission/common.handle_all_urls" ], "target": { "namespace": "android_app",

    "package_name": "com.capitainetrain.android", "sha256_cert_fingerprints": [ “5A:BF:2C:43:1A:2E:54:A3:60:31:58:A7:62:AA:4D:E0:AA:BE: 50:F7:00:36:3C:CB:41:CD:83:FE:F5:B9:58:2C” ] } } ] "sha256_cert_fingerprints": [ “5A:BF:2C:43:1A:2E:54:A3:60:31:58:A7:62:AA:4D:E0:AA:BE: 50:F7:00:36:3C:CB:41:CD:83:FE:F5:B9:58:2C” ]
  29. [ { "relation": [ "delegate_permission/common.handle_all_urls" ], "target": { "namespace": "android_app",

    "package_name": "com.capitainetrain.android", "sha256_cert_fingerprints": [ “5A:BF:2C:43:1A:2E:54:A3:60:31:58:A7:62:AA:4D:E0:AA:BE: 50:F7:00:36:3C:CB:41:CD:83:FE:F5:B9:58:2C” ] } } ]
  30. Available on Chrome 44+ Knows whether an app is installed

    Easy to add to your website Displayed when appropriate
  31. Available on Chrome 44+ Knows whether an app is installed

    Easy to add to your website Displayed when appropriate
  32. Available on Chrome 44+ Knows whether an app is installed

    Easy to add to your website Displayed when appropriate
  33. Available on Chrome 44+ Knows whether an app is installed

    Easy to add to your website Displayed when appropriate
  34. {
 "display": "browser",
 "icons": [
 {
 "src": "../favicons/android-icon-3x.png",
 "sizes": "144x144",


    "type": "image/png"
 }
 ],
 "name": "Trainline EU",
 "prefer_related_applications": true,
 "related_applications": [
 {
 "platform": "play",
 "id": "com.capitainetrain.android"
 }
 ],
 "short_name": "Trainline EU"
 }

  35. {
 "display": "browser",
 "icons": [
 {
 "src": "../favicons/android-icon-3x.png",
 "sizes": "144x144",


    "type": "image/png"
 }
 ],
 "name": "Trainline EU",
 "prefer_related_applications": true,
 "related_applications": [
 {
 "platform": "play",
 "id": "com.capitainetrain.android"
 }
 ],
 "short_name": "Trainline EU"
 }
 "icons": [
 {
 "src": "../favicons/android-icon-3x.png",
 "sizes": "144x144",
 "type": "image/png"
 }
 ],
  36. {
 "display": "browser",
 "icons": [
 {
 "src": "../favicons/android-icon-3x.png",
 "sizes": "144x144",


    "type": "image/png"
 }
 ],
 "name": "Trainline EU",
 "prefer_related_applications": true,
 "related_applications": [
 {
 "platform": "play",
 "id": "com.capitainetrain.android"
 }
 ],
 "short_name": "Trainline EU"
 }
 "short_name": "Trainline EU"
  37. {
 "display": "browser",
 "icons": [
 {
 "src": "../favicons/android-icon-3x.png",
 "sizes": "144x144",


    "type": "image/png"
 }
 ],
 "name": "Trainline EU",
 "prefer_related_applications": true,
 "related_applications": [
 {
 "platform": "play",
 "id": "com.capitainetrain.android"
 }
 ],
 "short_name": "Trainline EU"
 }
 "prefer_related_applications": true,
  38. {
 "display": "browser",
 "icons": [
 {
 "src": "../favicons/android-icon-3x.png",
 "sizes": "144x144",


    "type": "image/png"
 }
 ],
 "name": "Trainline EU",
 "prefer_related_applications": true,
 "related_applications": [
 {
 "platform": "play",
 "id": "com.capitainetrain.android"
 }
 ],
 "short_name": "Trainline EU"
 }
 "related_applications": [
 {
 "platform": "play",
 "id": "com.capitainetrain.android"
 }
 ],
  39. The user has visited your site twice over two separate

    days during the course of two weeks
  40. The user has visited your site twice over two separate

    days during the course of two weeks There is a dev option for that…
  41. The user has visited your site twice over two separate

    days during the course of two weeks There is a dev option for everything…
  42. The user has visited your site twice over two separate

    days during the course of two weeks There is a dev option chrome://flags/#bypass-app-banner-engagement-checks for everything…
  43. private final GoogleApiClient.ConnectionCallbacks mConnectionCallbacks = new GoogleApiClient.ConnectionCallbacks() { @Override public

    void onConnected(Bundle connectionHint) { final CredentialRequest request = new CredentialRequest.Builder(). setPasswordLoginSupported(true). build(); Auth.CredentialsApi.request(mCredentialsClient, request). setResultCallback(mRequestCallback); } @Override public void onConnectionSuspended(int cause) { // ... } }; private ResultCallback<CredentialRequestResult> mRequestCallback = new ResultCallback<CredentialRequestResult>() { @Override public void onResult(CredentialRequestResult result) { if (result.getStatus().isSuccess()) { onCredentialRetrieved(result.getCredential()); } else { resolveResult(result.getStatus()); } } };
  44. private final GoogleApiClient.ConnectionCallbacks mConnectionCallbacks = new GoogleApiClient.ConnectionCallbacks() { @Override public

    void onConnected(Bundle connectionHint) { final CredentialRequest request = new CredentialRequest.Builder(). setPasswordLoginSupported(true). build(); Auth.CredentialsApi.request(mCredentialsClient, request). setResultCallback(mRequestCallback); } @Override public void onConnectionSuspended(int cause) { // ... } }; private ResultCallback<CredentialRequestResult> mRequestCallback = new ResultCallback<CredentialRequestResult>() { @Override public void onResult(CredentialRequestResult result) { if (result.getStatus().isSuccess()) { onCredentialRetrieved(result.getCredential()); } else { resolveResult(result.getStatus()); } } }; public void onConnected(Bundle connectionHint) { final CredentialRequest request = new CredentialRequest.Builder(). setPasswordLoginSupported(true). build(); Auth.CredentialsApi.request(mCredentialsClient, request). setResultCallback(mRequestCallback); }
  45. private final GoogleApiClient.ConnectionCallbacks mConnectionCallbacks = new GoogleApiClient.ConnectionCallbacks() { @Override public

    void onConnected(Bundle connectionHint) { final CredentialRequest request = new CredentialRequest.Builder(). setPasswordLoginSupported(true). build(); Auth.CredentialsApi.request(mCredentialsClient, request). setResultCallback(mRequestCallback); } @Override public void onConnectionSuspended(int cause) { // ... } }; private ResultCallback<CredentialRequestResult> mRequestCallback = new ResultCallback<CredentialRequestResult>() { @Override public void onResult(CredentialRequestResult result) { if (result.getStatus().isSuccess()) { onCredentialRetrieved(result.getCredential()); } else { resolveResult(result.getStatus()); } } }; if (result.getStatus().isSuccess()) { onCredentialRetrieved(result.getCredential()); } else { resolveResult(result.getStatus()); }
  46. private void onCredentialRetrieved(Credential credential) { Auth.CredentialsApi.disableAutoSignIn(mCredentialsClient); signIn(credential.getId(), credential.getPassword()); } private

    void resolveResult(Status status) { if (status.getStatusCode() == CommonStatusCodes.RESOLUTION_REQUIRED) { try { status.startResolutionForResult(SmartLockActivity.this, RC_SLP_READ); } catch (IntentSender.SendIntentException e) { // Fail silently } } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case RC_SLP_READ: if (resultCode == RESULT_OK) { onCredentialRetrieved(data.<Credential>getParcelableExtra(Credential.EXTRA_KEY)); } break; } }
  47. private void onCredentialRetrieved(Credential credential) { Auth.CredentialsApi.disableAutoSignIn(mCredentialsClient); signIn(credential.getId(), credential.getPassword()); } private

    void resolveResult(Status status) { if (status.getStatusCode() == CommonStatusCodes.RESOLUTION_REQUIRED) { try { status.startResolutionForResult(SmartLockActivity.this, RC_SLP_READ); } catch (IntentSender.SendIntentException e) { // Fail silently } } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case RC_SLP_READ: if (resultCode == RESULT_OK) { onCredentialRetrieved(data.<Credential>getParcelableExtra(Credential.EXTRA_KEY)); } break; } } private void onCredentialRetrieved(Credential credential) { Auth.CredentialsApi.disableAutoSignIn(mCredentialsClient); signIn(credential.getId(), credential.getPassword()); }
  48. private void onCredentialRetrieved(Credential credential) { Auth.CredentialsApi.disableAutoSignIn(mCredentialsClient); signIn(credential.getId(), credential.getPassword()); } private

    void resolveResult(Status status) { if (status.getStatusCode() == CommonStatusCodes.RESOLUTION_REQUIRED) { try { status.startResolutionForResult(SmartLockActivity.this, RC_SLP_READ); } catch (IntentSender.SendIntentException e) { // Fail silently } } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case RC_SLP_READ: if (resultCode == RESULT_OK) { onCredentialRetrieved(data.<Credential>getParcelableExtra(Credential.EXTRA_KEY)); } break; } } private void resolveResult(Status status) { if (status.getStatusCode() == CommonStatusCodes.RESOLUTION_REQUIRED) { try { status.startResolutionForResult(SmartLockActivity.this, RC_SLP_READ); } catch (IntentSender.SendIntentException e) { // Fail silently } } }
  49. private void onCredentialRetrieved(Credential credential) { Auth.CredentialsApi.disableAutoSignIn(mCredentialsClient); signIn(credential.getId(), credential.getPassword()); } private

    void resolveResult(Status status) { if (status.getStatusCode() == CommonStatusCodes.RESOLUTION_REQUIRED) { try { status.startResolutionForResult(SmartLockActivity.this, RC_SLP_READ); } catch (IntentSender.SendIntentException e) { // Fail silently } } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case RC_SLP_READ: if (resultCode == RESULT_OK) { onCredentialRetrieved(data.<Credential>getParcelableExtra(Credential.EXTRA_KEY)); } break; } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case RC_SLP_READ: if (resultCode == RESULT_OK) { onCredentialRetrieved(data.<Credential>getParcelableExtra(Credential.EXTRA_KEY)); } break; } }
  50. private void saveCredential(String email, String password) { final Credential credential

    = new Credential.Builder(email). setPassword(password). build(); Auth.CredentialsApi.save(mCredentialsClient, credential). setResultCallback(mSavedCallback); } private final ResultCallback<Status> mSavedCallback = new ResultCallback<Status>() { @Override public void onResult(final Status status) { final Activity ziss = SmartLockActivity.this; if (status.isSuccess()) { Toast.makeText(ziss, "Your login credentials have been saved", Toast.LENGTH_LONG).show(); } else { if (status.hasResolution()) { try { status.startResolutionForResult(ziss, RC_SLP_WRITE); } catch (IntentSender.SendIntentException e) { // Fail silently } } } } };
  51. private void saveCredential(String email, String password) { final Credential credential

    = new Credential.Builder(email). setPassword(password). build(); Auth.CredentialsApi.save(mCredentialsClient, credential). setResultCallback(mSavedCallback); } private final ResultCallback<Status> mSavedCallback = new ResultCallback<Status>() { @Override public void onResult(final Status status) { final Activity ziss = SmartLockActivity.this; if (status.isSuccess()) { Toast.makeText(ziss, "Your login credentials have been saved", Toast.LENGTH_LONG).show(); } else { if (status.hasResolution()) { try { status.startResolutionForResult(ziss, RC_SLP_WRITE); } catch (IntentSender.SendIntentException e) { // Fail silently } } } } }; private void saveCredential(String email, String password) { final Credential credential = new Credential.Builder(email). setPassword(password). build(); Auth.CredentialsApi.save(mCredentialsClient, credential). setResultCallback(mSavedCallback); }
  52. private void saveCredential(String email, String password) { final Credential credential

    = new Credential.Builder(email). setPassword(password). build(); Auth.CredentialsApi.save(mCredentialsClient, credential). setResultCallback(mSavedCallback); } private final ResultCallback<Status> mSavedCallback = new ResultCallback<Status>() { @Override public void onResult(final Status status) { final Activity ziss = SmartLockActivity.this; if (status.isSuccess()) { Toast.makeText(ziss, "Your login credentials have been saved", Toast.LENGTH_LONG).show(); } else { if (status.hasResolution()) { try { status.startResolutionForResult(ziss, RC_SLP_WRITE); } catch (IntentSender.SendIntentException e) { // Fail silently } } } } }; if (status.isSuccess()) { Toast.makeText(ziss, "Your login credentials have been saved", Toast.LENGTH_LONG).show(); }
  53. private void saveCredential(String email, String password) { final Credential credential

    = new Credential.Builder(email). setPassword(password). build(); Auth.CredentialsApi.save(mCredentialsClient, credential). setResultCallback(mSavedCallback); } private final ResultCallback<Status> mSavedCallback = new ResultCallback<Status>() { @Override public void onResult(final Status status) { final Activity ziss = SmartLockActivity.this; if (status.isSuccess()) { Toast.makeText(ziss, "Your login credentials have been saved", Toast.LENGTH_LONG).show(); } else { if (status.hasResolution()) { try { status.startResolutionForResult(ziss, RC_SLP_WRITE); } catch (IntentSender.SendIntentException e) { // Fail silently } } } } }; if (status.hasResolution()) { try { status.startResolutionForResult(ziss, RC_SLP_WRITE); } catch (IntentSender.SendIntentException e) { // Fail silently } }
  54. private void deleteCredential(String email, String password) { final Credential credential

    = new Credential.Builder(email). setPassword(password). build(); Auth.CredentialsApi.delete(mCredentialsClient, credential); }
  55. It would be so magical to get credentials already saved

    on my website. Doing so would streamline the sign-in experience.
  56. It would be so magical to get credentials already saved

    on my website. Doing so would streamline the sign-in experience. Digital Assets Links to the rescue \o/
  57. [ { "relation": [ "delegate_permission/common.handle_all_urls" ], "target": { "namespace": "android_app",

    "package_name": "com.capitainetrain.android", "sha256_cert_fingerprints": [ “5A:BF:2C:43:1A:2E:54:A3:60:31:58:A7:62:AA:4D:E0:AA:BE: 50:F7:00:36:3C:CB:41:CD:83:FE:F5:B9:58:2C” ] } } ]
  58. [ { "relation": [ "delegate_permission/common.handle_all_urls" ], "target": { "namespace": "android_app",

    "package_name": "com.capitainetrain.android", "sha256_cert_fingerprints": [ “5A:BF:2C:43:1A:2E:54:A3:60:31:58:A7:62:AA:4D:E0:AA:BE: 50:F7:00:36:3C:CB:41:CD:83:FE:F5:B9:58:2C” ] } } ] " ", "delegate_permission/common.get_login_creds" ], "target": { "namespace": "android_app", "package_name": "com.capitainetrain.android", "sha256_cert_fingerprints": [ “5A:BF:2C:43:1A:2E:54:A3:60:31:58:A7:62:AA:4D:E0:AA:BE: 50:F7:00:36:3C:CB:41:CD:83:FE:F5:B9:58:2C” ] } } ]
  59. [ { "relation": [ "delegate_permission/common.handle_all_urls" ], "target": { "namespace": "android_app",

    "package_name": "com.capitainetrain.android", "sha256_cert_fingerprints": [ “5A:BF:2C:43:1A:2E:54:A3:60:31:58:A7:62:AA:4D:E0:AA:BE: 50:F7:00:36:3C:CB:41:CD:83:FE:F5:B9:58:2C” ] } } ] , “delegate_permission/common.get_login_creds" "delegate_permission/common.get_login_creds"
  60. It would be so magical to get credentials already saved

    on my website. Doing so would streamline the sign-in experience.
  61. Android app It would be so magical to get credentials

    already saved on my . Doing so would streamline the sign-in experience.
  62. Android app It would be so magical to get credentials

    already saved on my . Doing so would streamline the sign-in experience. Digital Assets Links to the rescue \o/ … again
  63. Don’t be afraid or lazy Tear down the walls between

    your web site and your native mobile apps.