На устройствах под управлением Android 8.0 (уровень API 26) и выше при сопряжении сопутствующих устройств выполняется сканирование ближайших устройств по Bluetooth или Wi-Fi от имени вашего приложения без необходимости разрешения ACCESS_FINE_LOCATION
. Это помогает максимизировать защиту конфиденциальности пользователей. Используйте этот метод для выполнения первоначальной настройки сопутствующего устройства, например умных часов с поддержкой BLE. Кроме того, для сопряжения сопутствующих устройств необходимо включить службы геолокации.
Сопряжение сопутствующих устройств само по себе не создает соединений и не обеспечивает непрерывное сканирование. Приложения могут использовать API-интерфейсы подключения Bluetooth или Wi-Fi для установления соединений.
После сопряжения устройства оно может использовать разрешения REQUEST_COMPANION_RUN_IN_BACKGROUND
и REQUEST_COMPANION_USE_DATA_IN_BACKGROUND
для запуска приложения в фоновом режиме. Приложения также могут использовать разрешение REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND
для запуска службы переднего плана из фона.
Пользователь может выбрать устройство из списка и предоставить приложению разрешения на доступ к устройству. Эти разрешения отменяются, если вы удаляете приложение или вызываете disassociate()
. Сопутствующее приложение отвечает за очистку своих собственных ассоциаций, если они больше не нужны пользователю, например, когда он выходит из системы или удаляет привязанные устройства.
Реализация сопряжения сопутствующих устройств
В этом разделе объясняется, как использовать CompanionDeviceManager
для сопряжения вашего приложения с сопутствующими устройствами через Bluetooth, BLE и Wi-Fi.
Укажите сопутствующие устройства
В следующем примере кода показано, как добавить флаг <uses-feature>
в файл манифеста. Это сообщает системе, что ваше приложение намерено настроить сопутствующие устройства.
<uses-feature android:name="android.software.companion_device_setup"/>
Получение списка устройств по DeviceFilter
Вы можете отобразить все сопутствующие устройства, находящиеся в радиусе действия, которые соответствуют предоставленному вами DeviceFilter
(показано на рис. 1). Если вы хотите ограничить сканирование только одним устройством, вы можете setSingleDevice()
значение true
(показано на рисунке 2).
![Сопряжение сопутствующих устройств](https://developer.android.com/static/images/develop/connectivity/companion-device-pairing-1.png?authuser=0000&hl=ru)
![Сопряжение одного устройства](https://developer.android.com/static/images/develop/connectivity/companion-device-pairing-2.png?authuser=0000&hl=ru)
Ниже приведены подклассы DeviceFilter
, которые можно указать в AssociationRequest
:
Все три подкласса имеют конструкторы, упрощающие настройку фильтров. В следующем примере устройство сканирует устройство Bluetooth с помощью BluetoothDeviceFilter
.
Котлин
val deviceFilter: BluetoothDeviceFilter = BluetoothDeviceFilter.Builder() // Match only Bluetooth devices whose name matches the pattern. .setNamePattern(Pattern.compile("My device")) // Match only Bluetooth devices whose service UUID matches this pattern. .addServiceUuid(ParcelUuid(UUID(0x123abcL, -1L)), null) .build()
Ява
BluetoothDeviceFilter deviceFilter = new BluetoothDeviceFilter.Builder() // Match only Bluetooth devices whose name matches the pattern. .setNamePattern(Pattern.compile("My device")) // Match only Bluetooth devices whose service UUID matches this pattern. .addServiceUuid(new ParcelUuid(new UUID(0x123abcL, -1L)), null) .build();
Установите для DeviceFilter
значение AssociationRequest
, чтобы CompanionDeviceManager
мог определить, какой тип устройств искать.
Котлин
val pairingRequest: AssociationRequest = AssociationRequest.Builder() // Find only devices that match this request filter. .addDeviceFilter(deviceFilter) // Stop scanning as soon as one device matching the filter is found. .setSingleDevice(true) .build()
Ява
AssociationRequest pairingRequest = new AssociationRequest.Builder() // Find only devices that match this request filter. .addDeviceFilter(deviceFilter) // Stop scanning as soon as one device matching the filter is found. .setSingleDevice(true) .build();
После того как ваше приложение инициализирует AssociationRequest
, запустите функцию associate()
в CompanionDeviceManager
. Функция associate()
принимает AssociationRequest
и Callback
.
Callback
возвращает IntentSender
в onAssociationPending
, когда CompanionDeviceManager
обнаруживает устройство и готово запустить диалоговое окно согласия пользователя. После того как пользователь подтвердит устройство, в onAssociationCreated
возвращается AssociationInfo
устройства. Если ваше приложение не находит ни одного устройства, обратный вызов возвращает onFailure
с сообщением об ошибке.
На устройствах под управлением Android 13 (уровень API 33) и выше:
Котлин
val deviceManager = requireContext().getSystemService(Context.COMPANION_DEVICE_SERVICE) val executor: Executor = Executor { it.run() } deviceManager.associate(pairingRequest, executor, object : CompanionDeviceManager.Callback() { // Called when a device is found. Launch the IntentSender so the user // can select the device they want to pair with. override fun onAssociationPending(intentSender: IntentSender) { intentSender?.let { startIntentSenderForResult(it, SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0) } } override fun onAssociationCreated(associationInfo: AssociationInfo) { // An association is created. } override fun onFailure(errorMessage: CharSequence?) { // To handle the failure. } })
Ява
CompanionDeviceManager deviceManager = (CompanionDeviceManager) getSystemService(Context.COMPANION_DEVICE_SERVICE); Executor executor = new Executor() { @Override public void execute(Runnable runnable) { runnable.run(); } }; deviceManager.associate(pairingRequest, new CompanionDeviceManager.Callback() { executor, // Called when a device is found. Launch the IntentSender so the user can // select the device they want to pair with. @Override public void onDeviceFound(IntentSender chooserLauncher) { try { startIntentSenderForResult( chooserLauncher, SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0 ); } catch (IntentSender.SendIntentException e) { Log.e("MainActivity", "Failed to send intent"); } } @Override public void onAssociationCreated(AssociationInfo associationInfo) { // An association is created. } @Override public void onFailure(CharSequence errorMessage) { // To handle the failure. });
На устройствах под управлением Android 12L (уровень API 32) или ниже (устарело):
Котлин
val deviceManager = requireContext().getSystemService(Context.COMPANION_DEVICE_SERVICE) deviceManager.associate(pairingRequest, object : CompanionDeviceManager.Callback() { // Called when a device is found. Launch the IntentSender so the user // can select the device they want to pair with. override fun onDeviceFound(chooserLauncher: IntentSender) { startIntentSenderForResult(chooserLauncher, SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0) } override fun onFailure(error: CharSequence?) { // To handle the failure. } }, null)
Ява
CompanionDeviceManager deviceManager = (CompanionDeviceManager) getSystemService(Context.COMPANION_DEVICE_SERVICE); deviceManager.associate(pairingRequest, new CompanionDeviceManager.Callback() { // Called when a device is found. Launch the IntentSender so the user can // select the device they want to pair with. @Override public void onDeviceFound(IntentSender chooserLauncher) { try { startIntentSenderForResult( chooserLauncher, SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0 ); } catch (IntentSender.SendIntentException e) { Log.e("MainActivity", "Failed to send intent"); } } @Override public void onFailure(CharSequence error) { // To handle the failure. } }, null);
Результат выбора пользователя отправляется обратно во фрагмент onActivityResult()
вашей активности. После этого вы сможете получить доступ к выбранному устройству.
Когда пользователь выбирает устройство Bluetooth, ожидайте BluetoothDevice
. Когда пользователь выбирает устройство Bluetooth LE, ожидается android.bluetooth.le.ScanResult
. Когда пользователь выбирает устройство Wi-Fi, ожидайте android.net.wifi.ScanResult
.
Котлин
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { when (requestCode) { SELECT_DEVICE_REQUEST_CODE -> when(resultCode) { Activity.RESULT_OK -> { // The user chose to pair the app with a Bluetooth device. val deviceToPair: BluetoothDevice? = data?.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE) deviceToPair?.let { device -> device.createBond() // Continue to interact with the paired device. } } } else -> super.onActivityResult(requestCode, resultCode, data) } }
Ява
@Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { if (resultCode != Activity.RESULT_OK) { return; } if (requestCode == SELECT_DEVICE_REQUEST_CODE && data != null) { BluetoothDevice deviceToPair = data.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE); if (deviceToPair != null) { deviceToPair.createBond(); // Continue to interact with the paired device. } } else { super.onActivityResult(requestCode, resultCode, data); } }
См. полный пример:
На устройствах под управлением Android 13 (уровень API 33) и выше:
Котлин
private const val SELECT_DEVICE_REQUEST_CODE = 0 class MainActivity : AppCompatActivity() { private val deviceManager: CompanionDeviceManager by lazy { getSystemService(Context.COMPANION_DEVICE_SERVICE) as CompanionDeviceManager } val mBluetoothAdapter: BluetoothAdapter by lazy { val java = BluetoothManager::class.java getSystemService(java)!!.adapter } val executor: Executor = Executor { it.run() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // To skip filters based on names and supported feature flags (UUIDs), // omit calls to setNamePattern() and addServiceUuid() // respectively, as shown in the following Bluetooth example. val deviceFilter: BluetoothDeviceFilter = BluetoothDeviceFilter.Builder() .setNamePattern(Pattern.compile("My device")) .addServiceUuid(ParcelUuid(UUID(0x123abcL, -1L)), null) .build() // The argument provided in setSingleDevice() determines whether a single // device name or a list of them appears. val pairingRequest: AssociationRequest = AssociationRequest.Builder() .addDeviceFilter(deviceFilter) .setSingleDevice(true) .build() // When the app tries to pair with a Bluetooth device, show the // corresponding dialog box to the user. deviceManager.associate(pairingRequest, executor, object : CompanionDeviceManager.Callback() { // Called when a device is found. Launch the IntentSender so the user // can select the device they want to pair with. override fun onAssociationPending(intentSender: IntentSender) { intentSender?.let { startIntentSenderForResult(it, SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0) } } override fun onAssociationCreated(associationInfo: AssociationInfo) { // AssociationInfo object is created and get association id and the // macAddress. var associationId: int = associationInfo.id var macAddress: MacAddress = associationInfo.deviceMacAddress } override fun onFailure(errorMessage: CharSequence?) { // Handle the failure. } ) override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { when (requestCode) { SELECT_DEVICE_REQUEST_CODE -> when(resultCode) { Activity.RESULT_OK -> { // The user chose to pair the app with a Bluetooth device. val deviceToPair: BluetoothDevice? = data?.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE) deviceToPair?.let { device -> device.createBond() // Maintain continuous interaction with a paired device. } } } else -> super.onActivityResult(requestCode, resultCode, data) } } }
Ява
class MainActivityJava extends AppCompatActivity { private static final int SELECT_DEVICE_REQUEST_CODE = 0; Executor executor = new Executor() { @Override public void execute(Runnable runnable) { runnable.run(); } }; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); CompanionDeviceManager deviceManager = (CompanionDeviceManager) getSystemService( Context.COMPANION_DEVICE_SERVICE ); // To skip filtering based on name and supported feature flags, // do not include calls to setNamePattern() and addServiceUuid(), // respectively. This example uses Bluetooth. BluetoothDeviceFilter deviceFilter = new BluetoothDeviceFilter.Builder() .setNamePattern(Pattern.compile("My device")) .addServiceUuid( new ParcelUuid(new UUID(0x123abcL, -1L)), null ) .build(); // The argument provided in setSingleDevice() determines whether a single // device name or a list of device names is presented to the user as // pairing options. AssociationRequest pairingRequest = new AssociationRequest.Builder() .addDeviceFilter(deviceFilter) .setSingleDevice(true) .build(); // When the app tries to pair with the Bluetooth device, show the // appropriate pairing request dialog to the user. deviceManager.associate(pairingRequest, new CompanionDeviceManager.Callback() { executor, // Called when a device is found. Launch the IntentSender so the user can // select the device they want to pair with. @Override public void onDeviceFound(IntentSender chooserLauncher) { try { startIntentSenderForResult( chooserLauncher, SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0 ); } catch (IntentSender.SendIntentException e) { Log.e("MainActivity", "Failed to send intent"); } } @Override public void onAssociationCreated(AssociationInfo associationInfo) { // AssociationInfo object is created and get association id and the // macAddress. int associationId = associationInfo.getId(); MacAddress macAddress = associationInfo.getDeviceMacAddress(); } @Override public void onFailure(CharSequence errorMessage) { // Handle the failure. }); } @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { if (resultCode != Activity.RESULT_OK) { return; } if (requestCode == SELECT_DEVICE_REQUEST_CODE) { if (resultCode == Activity.RESULT_OK && data != null) { BluetoothDevice deviceToPair = data.getParcelableExtra( CompanionDeviceManager.EXTRA_DEVICE ); if (deviceToPair != null) { deviceToPair.createBond(); // ... Continue interacting with the paired device. } } } else { super.onActivityResult(requestCode, resultCode, data); } } }
На устройствах под управлением Android 12L (уровень API 32) или ниже (устарело):
Котлин
private const val SELECT_DEVICE_REQUEST_CODE = 0 class MainActivity : AppCompatActivity() { private val deviceManager: CompanionDeviceManager by lazy { getSystemService(Context.COMPANION_DEVICE_SERVICE) as CompanionDeviceManager } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // To skip filters based on names and supported feature flags (UUIDs), // omit calls to setNamePattern() and addServiceUuid() // respectively, as shown in the following Bluetooth example. val deviceFilter: BluetoothDeviceFilter = BluetoothDeviceFilter.Builder() .setNamePattern(Pattern.compile("My device")) .addServiceUuid(ParcelUuid(UUID(0x123abcL, -1L)), null) .build() // The argument provided in setSingleDevice() determines whether a single // device name or a list of them appears. val pairingRequest: AssociationRequest = AssociationRequest.Builder() .addDeviceFilter(deviceFilter) .setSingleDevice(true) .build() // When the app tries to pair with a Bluetooth device, show the // corresponding dialog box to the user. deviceManager.associate(pairingRequest, object : CompanionDeviceManager.Callback() { override fun onDeviceFound(chooserLauncher: IntentSender) { startIntentSenderForResult(chooserLauncher, SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0) } override fun onFailure(error: CharSequence?) { // Handle the failure. } }, null) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { when (requestCode) { SELECT_DEVICE_REQUEST_CODE -> when(resultCode) { Activity.RESULT_OK -> { // The user chose to pair the app with a Bluetooth device. val deviceToPair: BluetoothDevice? = data?.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE) deviceToPair?.let { device -> device.createBond() // Maintain continuous interaction with a paired device. } } } else -> super.onActivityResult(requestCode, resultCode, data) } } }
Ява
class MainActivityJava extends AppCompatActivity { private static final int SELECT_DEVICE_REQUEST_CODE = 0; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); CompanionDeviceManager deviceManager = (CompanionDeviceManager) getSystemService( Context.COMPANION_DEVICE_SERVICE ); // To skip filtering based on name and supported feature flags, // don't include calls to setNamePattern() and addServiceUuid(), // respectively. This example uses Bluetooth. BluetoothDeviceFilter deviceFilter = new BluetoothDeviceFilter.Builder() .setNamePattern(Pattern.compile("My device")) .addServiceUuid( new ParcelUuid(new UUID(0x123abcL, -1L)), null ) .build(); // The argument provided in setSingleDevice() determines whether a single // device name or a list of device names is presented to the user as // pairing options. AssociationRequest pairingRequest = new AssociationRequest.Builder() .addDeviceFilter(deviceFilter) .setSingleDevice(true) .build(); // When the app tries to pair with the Bluetooth device, show the // appropriate pairing request dialog to the user. deviceManager.associate(pairingRequest, new CompanionDeviceManager.Callback() { @Override public void onDeviceFound(IntentSender chooserLauncher) { try { startIntentSenderForResult(chooserLauncher, SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0); } catch (IntentSender.SendIntentException e) { // failed to send the intent } } @Override public void onFailure(CharSequence error) { // handle failure to find the companion device } }, null); } @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { if (requestCode == SELECT_DEVICE_REQUEST_CODE) { if (resultCode == Activity.RESULT_OK && data != null) { BluetoothDevice deviceToPair = data.getParcelableExtra( CompanionDeviceManager.EXTRA_DEVICE ); if (deviceToPair != null) { deviceToPair.createBond(); // ... Continue interacting with the paired device. } } } else { super.onActivityResult(requestCode, resultCode, data); } } }
Профили сопутствующих устройств
В Android 12 (уровень API 31) и более поздних версиях сопутствующие приложения, которые управляют такими устройствами, как часы, могут использовать профили сопутствующих устройств, чтобы упростить процесс настройки, предоставляя необходимые разрешения при сопряжении. Дополнительную информацию см. в разделе Профили сопутствующих устройств .
Не отключайте сопутствующие приложения
В Android 12 (уровень API 31) и более поздних версиях вы можете использовать дополнительные API, чтобы ваше сопутствующее приложение продолжало работать, пока сопутствующее устройство находится в пределах досягаемости. Эти API позволяют вам делать следующее:
Выводите приложение из спящего режима, когда сопутствующее устройство находится в пределах досягаемости.
Дополнительные сведения см.
CompanionDeviceManager.startObservingDevicePresence()
.Гарантируйте, что процесс приложения будет продолжать работать, пока сопутствующее устройство находится в пределах досягаемости.
Подробности см. в
CompanionDeviceService.onDeviceAppeared()
.