Привет, Хабр!

Сегодня я расскажу про API для разработчиков от компании Google. Речь пойдёт о том, как не заставлять пользователя заново логиниться в приложении после переноса данных, или, выражаясь точнее, как использовать Android Account Transfer API.

Скорее всего, каждый из нас покупал новый смартфон и ему приходилось переносить на него всю важную информацию и приложения со старого. Сейчас этот процесс стал достаточно простым благодаря технологии Tap & Go. Но есть одно но. Приходится заново логиниться везде, где только можно. А что если это приложение типа фитнес-трекера, где залогинился один раз и забыл? Восстанавливать пароль? Опять головная боль. Вы можете сказать: «Но есть же Smart Lock!», и будете правы, но мы же должны учесть все кейсы. Что если человек забыл сохранить пароль? Или он просто параноик и не хранит пароли? Или в приложении не реализован Smart Lock? Думаю, что всегда найдутся причины забыть авторизационные данные. Но теперь решение есть, и вы сможете облегчить бремя переноса авторизационных данных ваших пользователей. Только вот оно не для всех. Да и эффективно заработает как минимум через год.

Из этого вытекает три вопроса:

1. Что вообще из себя представляет Account Transfer API?
2. Почему он не для всех?
3. Почему эффективно заработает минимум через год?

Необходимые требования


Как я уже говорил, Account Transfer API позволяет разработчикам передать авторизационные данные, включая токен и информацию об аккаунте, со старого телефона на новый. А что для этого нужно?

И тут мы упираемся в самую грустную часть, необходимую для реализации этой технологии.

Что нужно для того, чтобы всё заработало?

1. Старый девайс с версией Android не ниже 4.0.1 (API Level 14).
Не такая уж и проблема, скажете вы, и я с этим соглашусь, но смотрим далее.

2. Новый девайс, на который мы хотим скопировать данные, должен быть под управлением ОС не ниже Android 8.0 (API Level 26).

Это и есть ответ на вопрос «Почему эффективно заработает минимум через год?». Да, в этом году многие девайсы получат обновление Android до Oreo, но вряд ли они будут продаваться с уже встроенной ОС с необходимым API Level. Скорее всего, на прилавках так и останутся стоковые версии, у которых «на борту» будет максимум Android Marshmallow. И после покупки обновлять их придётся вручную. А это ключевой момент для нашей фичи. Очень велика вероятность, что пользователь сначала перенесёт свои данные на устройство, а уже потом обновит его.

Чтобы не быть голословным, покажу вам информацию Google от 5 февраля 2018.



3. На обоих устройствах должен быть Google Play версии 11.2.0 и выше. Это не должно стать большой проблемой, если, конечно, вы не поддерживаете устройства без Google Play. На них трансфер организовать не получится.

4. Вы должны собрать свой APK, используя сервисы Google Play не ниже 11.2.0. Может стать проблемой только в случае, если у вас есть причины не использовать сервисы данной версии или не использовать сервисы вообще.

5. Необходим уже реализованный AbstractAccountAuthenticator, интегрированный с AccountManager. Есть отличный цикл статей на «Хабре» по реализации.

Как это работает?




При условии, что все требования выполнены, между телефонами создаётся безопасное проводное соединение или зашифрованное через Bluetooth. Если во время трансфера вы хотите проверить валидность токена у себя на бэкенде, то одно из устройств должно быть подключено к интернету.

Далее система отсылает Broadcast-сообщение (ACTION_START_ACCOUNT_EXPORT или ACTION_ACCOUNT_EXPORT_DATA_AVAILABLE) о готовности начать экспорт данных, при получении которого мы должны запустить Foreground Service, предназначенный для обмена данными. Т.е. нам нужно сформировать и отправить данные аккаунта пользователя через защищённое соединение на устройство-получатель.





После этого система присылает ещё одно Broadcast-сообщение (ACTION_ACCOUNT_IMPORT_DATA_AVAILABLE) уже на устройство-получатель. Здесь также требуется запустить Foreground Service и обработать полученную информацию.



Если возникает ситуация, при которой сервис на устройстве-получателе не может быть создан на момент транспортировки данных, то система сохранит данные пользователя во временном хранилище. Данные будут получены при первом запуске приложения.



Реализация


Наконец вы решаетесь приступить к реализации.

Для начала необходимо подключить play-services-auth, если это не было сделано ранее.

dependencies {
  // For Account Transfer api, use SDK version 11.2.0 or higher
  compile 'com.google.android.gms:play-services-auth-base:<VERSION_NUMBER>'
}

Далее объявить в манифесте BroadcastReceiver и Service:

<receiver
     android:name=".AccountTransferBroadcastReceiver"
     android:enabled="true"
     android:exported="true" >
  <intent-filter>
     <action android:name="com.google.android.gms.auth.START_ACCOUNT_EXPORT" />
  </intent-filter>
  <intent-filter>
     <action android:name="com.google.android.gms.auth.ACCOUNT_IMPORT_DATA_AVAILABLE" />
  </intent-filter>
  <intent-filter>
     <action android:name="com.google.android.gms.auth.ACCOUNT_EXPORT_DATA_AVAILABLE" />
  </intent-filter>
</receiver>

<service android:name=".AuthenticatorService">
  <intent-filter>
     <action android:name="android.accounts.AccountAuthenticator"/>
  </intent-filter>
  <meta-data
        android:name="android.accounts.AccountAuthenticator"
        android:resource="@xml/authenticator"/>
</service>

Как было описано выше, сервис нужно запускать при получении соответствующих Broadcast-сообщений:

public class AccountTransferBroadcastReceiver extends BroadcastReceiver {
  @Override
  public void onReceive(Context context, Intent intent) {
     // Long running tasks, like calling Account Transfer API, shouldn't happen here. Start a
     // foreground service to perform long running tasks.
     Intent serviceIntent = AccountTransferService.getIntent(context, intent.getAction());
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        context.startForegroundService(serviceIntent);
     } else {
        context.startService(serviceIntent);
     }
  }
}

Ну и соответственно, частичная реализация сервиса:

protected void onHandleIntent(Intent intent) {
  String action = intent.getAction();
  if (action == null) {
     return;
  }
 
  switch (action) {
     case AccountTransfer.ACTION_ACCOUNT_IMPORT_DATA_AVAILABLE:
        importAccount();
        return;
     case ACTION_START_ACCOUNT_EXPORT:
     case AccountTransfer.ACTION_ACCOUNT_EXPORT_DATA_AVAILABLE:
        exportAccount();
        return;
  }
}

private void importAccount() {
  // Handle to client object
  AccountTransferClient client = AccountTransfer.getAccountTransferClient(this);
 
  // Make RetrieveData api call to get the transferred over data.
  Task<byte[]> transferTask = client.retrieveData(ACCOUNT_TYPE);
  try {
     byte[] transferBytes = Tasks.await(transferTask, TIMEOUT_API, TIME_UNIT);
     //import logic
  } catch (ExecutionException | InterruptedException | TimeoutException | JSONException e) {
     client.notifyCompletion(
           ACCOUNT_TYPE, AuthenticatorTransferCompletionStatus.COMPLETED_FAILURE);
     return;
  }
  client.notifyCompletion(
        ACCOUNT_TYPE, AuthenticatorTransferCompletionStatus.COMPLETED_SUCCESS);
 
}

private void exportAccount() {
  byte[] transferBytes = Foo.yourOwnMethodToGetAccountTransferBytes();
  AccountTransferClient client = AccountTransfer.getAccountTransferClient(this);
  if (transferBytes == null) {
     // Notifying is important.
     client.notifyCompletion(
           ACCOUNT_TYPE, AuthenticatorTransferCompletionStatus.COMPLETED_SUCCESS);
     return;
  }
 
  // Send the data over to the other device.
  Task<Void> exportTask = client.sendData(ACCOUNT_TYPE, transferBytes);
  try {
     Tasks.await(exportTask, TIMEOUT_API, TIME_UNIT);
  } catch (ExecutionException | InterruptedException | TimeoutException e) {
     // Notifying is important.
     client.notifyCompletion(
           ACCOUNT_TYPE, AuthenticatorTransferCompletionStatus.COMPLETED_FAILURE);
     return;
  }
}

Более подробный пример можно найти в репозитории googlesamples.

Для тестирования Google предлагает запустить Setup Wizard командой.

$ adb shell am start -a android.intent.action.MAIN -n com.google.android.gms/.smartdevice.d2d.ui.TargetActivity

И всё, этого достаточно, чтобы ваш пользователь забыл о головной боли и лишних действиях при работе с вашим приложением после его переустановки на новый девайс. Возможно, именно это и будет ключевым моментом, который даст понять пользователю, продолжать ли пользоваться приложением или удалить его и забыть.

Подводя итог, отмечу, что Google хочет максимально сократить время, которое пользователь тратит на повторную авторизацию, и делает логичные шаги для этого. И хорошо бы поддержать их в этом направлении. Хоть дело и требует некоторых временных затрат на реализацию, но оно того стоит. Ведь дьявол кроется в деталях.

Интересная информация по этой теме:

Документация
Видеовведение
Пример реализации

Комментарии (7)


  1. zagayevskiy
    16.02.2018 12:17

    При условии, что все требования выполнены, между телефонами создаётся безопасное проводное соединение или зашифрованное через Bluetooth

    Кто-то ещё пользуется блютузом для трансфера чего-либо между девайсами? в то время, как всё уже давно лежит в облаке и для переезда с одного телефона на другой надо всего лишь залогиниться? Бесполезная фича какая-то, реализовывать её для 0.01% пользователей? Какой смысл?


    1. 1cubik
      16.02.2018 16:06
      +1

      Если Вы имели ввиду девайсы впринципе(а не конкретно передачу телефон-телефон), то Bluetooth широко используется в Android для работы, например, носимых устройств, а также для подключения к различным мультимедиа-системам.
      А смысл использовать Bluetooth, как минимум, оправдан с точки зрения безопасности. В том числе, если данные передаются между двумя телефонами некоторого %username%, то в обоих находится именно его аккаунт, здесь и сейчас, в одной комнате. В каком-то смысле, дополнительный уровень «верификации» пользователя. Потому что телефон, условно, можно украсть и через «облако» получить доступ к этим паролям.


      1. zagayevskiy
        16.02.2018 16:45

        если данные передаются между двумя телефонами некоторого %username%, то в обоих находится именно его аккаунт, здесь и сейчас, в одной комнате. В каком-то смысле, дополнительный уровень «верификации» пользователя. Потому что телефон, условно, можно украсть и через «облако» получить доступ к этим паролям.

        Если на двух девайсах один аккаунт, то не вижу проблемы через облако переслать токены, шифрованные паролем этого пользователя. А точнее, такая схема — на девайсе хранится ключ, которым шифруются токены. Этот же ключ лежит в облаке, зашифрованный паролем пользователя. Тогда ни гугл, ни злоумышленники не смогут получить доступ к токенам.


        1. 1cubik
          16.02.2018 17:26

          Если Вы имеете ввиду «пароль этого пользователя» == пароль от гугл-аккаунта, то тогда есть проблема и очень большая, если приложение получило пароль в явном виде =)
          В противном случае этот «пароль» должен быть одноразовый(по причине того, что телефон так же могут украсть, а, следовательно, получить доступ к многоразовуму паролю, следовательно, к Вашим данным), а это уже становится опять похожим, на мой взгляд, на технологию Tap&Login&Go, о которой говорит автор. Только «логиниться» нужно для синхронизации телефонов, а не в каждом аккаунте.
          Ваше решение ни плохое, ни хорошее, оно другое. Альтернативное. Но в том решении, которое предложил автор, гораздо меньше промежуточных звеньев между телефоном-донором и телефоном-реципиентом данных, а именно равно нулю. Более того, отсутствует необходимость использовать дополнительную инфраструктуру для реализации идеи в виде «облака». А это деньги, разработчики, поддержка…


          1. zagayevskiy
            16.02.2018 17:33

            Телефон могут украсть и получить доступ к чему? К тем приложениям, в которых вы и так залогинены и к ключу, которым зашифрованы токены этих приложений — то есть безопасность не ухудшается. Обычный keychain. И нет необходимости думать о том, чтобы переносить данные, париться с блютузом, который вечно не работает и совершать магические пассы. Залогинился один раз и получил привычное окружение.


            Облако у гугла уже есть и синхронизация в него тоже.


    1. smartdev Автор
      16.02.2018 16:35

      В облаке хранятся только данные, но не приложения. Поправьте, если ошибаюсь, но приложения привязаны к Google Play аккаунту. И если со старого телефона не переносить их через Tap&Go, то придется устанавливать их руками, хоть они и будут списком в Вашем аккаунте и нужно будет только потыкать «установить», но все же с облака они не подтянутся. При использовании Tap&Go необходим NFC и Bluetooth. И получается после реализации этой фичи будет действительно Tap&Go, а не Tap&Login&Go, как сейчас с большинством приложений. Я не говорю, что это панацея и каждый должен это у себя пилить. Если Вы считаете реализацию этой фичи пустой тратой времени — Ваше право, ведь мое мнение не претендует на истину в последней инстанции :)


      1. zagayevskiy
        16.02.2018 16:40

        Ну так и реализовали бы API для сохранения токенов в облако. Шифровать можно гугловым паролем пользователя, например.