Android 8.0 (API 수준 26) 이상을 실행하는 기기에서는 호환 기기 페어링이 ACCESS_FINE_LOCATION
권한을 요구하지 않고도 앱을 대신하여 근처 기기의 블루투스 또는 Wi-Fi 검색을 실행합니다. 이는 사용자 개인 정보 보호 기능을 극대화하는 데 도움이 됩니다. 기기가 페어링되면 기기는 REQUEST_COMPANION_RUN_IN_BACKGROUND
및 REQUEST_COMPANION_USE_DATA_IN_BACKGROUND
권한을 활용하여 백그라운드에서 앱을 시작할 수 있습니다. 이 메서드를 사용하여 BLE 지원 스마트시계와 같은 호환 기기의 초기 구성을 실행합니다. 또한 호환 기기 페어링을 사용하려면 위치 서비스를 사용 설정해야 합니다.
호환 기기 페어링은 자체적으로 연결을 만들지 않습니다. 블루투스 및 Wi-Fi 연결 API가 연결을 설정합니다. 컴패니언 기기 페어링에서도 연속 스캔을 사용 설정하지 않습니다.
사용자는 목록에서 기기를 선택하고 앱에 액세스할 권한을 부여할 수 있습니다. 이러한 권한은 앱을 제거하거나 disassociate()
를 호출하면 취소됩니다.
앱은 사용자가 로그아웃하거나 연결된 기기를 삭제하는 등 더 이상 필요하지 않은 경우 자체 연결을 삭제해야 합니다.
부속 기기 페어링 구현
호환 기기의 연결을 설정하고 관리하려면 CompanionDeviceManager
를 사용하세요.
이 섹션에서는 블루투스, BLE, Wi-Fi를 통해 호환 기기와 앱을 페어링할 때 페어링 요청 대화상자를 맞춤설정하는 방법을 설명합니다.
호환 기기 지정
다음 코드 샘플은 <uses-feature>
플래그를 매니페스트 파일에 추가하는 방법을 보여줍니다. 이렇게 하면 앱에서 호환 기기를 설정하려고 한다고 시스템에 알립니다.
<uses-feature android:name="android.software.companion_device_setup"/>
유형별로 기기 나열
제공한 필터와 일치하는 사용 가능한 모든 호환 기기를 표시하거나 표시를 단일 옵션으로 제한할 수 있습니다 (그림 1 참고). 앱이 찾고 있는 기기 유형을 지정하는 필터를 만들거나 setSingleDevice()
를 true
로 설정하여 구성합니다 (그림 2 참고).
요청 대화상자에 표시되는 호환 기기 목록에 필터를 적용하려면 블루투스가 켜져 있는지 확인하거나 Wi-Fi가 켜져 있는지 확인합니다.
연결이 사용 설정되면 DeviceFilter
를 추가할 수 있습니다. DeviceFilter
의 다음 서브클래스는 연결 유형에 따라 앱이 연결할 수 있는 기기 유형을 지정합니다.
세 가지 서브클래스 모두 필터 구성을 간소화하는 빌더를 가지고 있습니다.
다음 예에서 기기는 BluetoothDeviceFilter
가 있는 블루투스 기기를 검색합니다.
Kotlin
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()
Java
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
로 설정합니다.
Kotlin
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()
Java
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
를 초기화한 후 CompanionDeviceManager
에서 associate()
함수를 실행합니다. associate()
함수는 페어링 요청 객체 및 콜백을 사용합니다. 콜백은 앱이 기기를 찾고 사용자가 선택사항을 입력할 수 있는 대화상자를 시작할 준비가 되었음을 나타냅니다.
앱에서 기기를 찾지 못하면 콜백이 오류 메시지를 반환합니다.
Android 13 (API 수준 33) 이상을 실행하는 기기:
Kotlin
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) { // The association is created. } override fun onFailure(errorMessage: CharSequence?) { // Handle the failure. } })
Java
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) { // The association is created. } @Override public void onFailure(CharSequence errorMessage) { // Handle the failure. });
Android 12L (API 수준 32) 이하 (지원 중단됨)를 실행하는 기기:
Kotlin
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?) { // Handle the failure. } }, null)
Java
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) { // Handle the failure. } }, null);
사용자가 연결할 기기 유형을 선택할 수 있도록 하려면 onAssociationPending()
함수에서 intentSender
매개변수를 사용하여 환경설정 활동을 시작합니다. 이 작업의 결과는 환경설정 활동의 onActivityResult()
함수에 있는 프래그먼트로 다시 전송됩니다. 이렇게 하면 사용자가 결과에 따라 선택할 때 업데이트됩니다. 그런 다음 선택한 기기에 액세스할 수 있습니다. 사용자가 블루투스 기기를 선택하면 전송되는 결과는 BluetoothDevice
객체입니다.
마찬가지로 onAssociationPending()
함수가 사용자가 블루투스 LE 기기를 선택했음을 감지하면 android.bluetooth.le.ScanResult
객체를 예상합니다. Wi-Fi 기기의 경우 android.net.wifi.ScanResult
객체가 필요합니다.
Kotlin
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) } }
Java
@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) 이상을 실행하는 기기:
Kotlin
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) } } }
Java
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) 이하 (지원 중단됨)를 실행하는 기기:
Kotlin
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) } } }
Java
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) 이상 버전에서 실행되는 파트너 앱은 시계에 연결할 때 호환 기기 프로필을 사용할 수 있습니다. 자세한 내용은 Wear OS에서 권한 요청 가이드를 참고하세요.
호환 앱을 켜진 상태로 유지
Android 12 (API 수준 31) 이상에서는 추가 API를 사용하여 호환 기기가 범위 내에 있는 동안 호환 앱이 계속 실행되도록 할 수 있습니다. 이러한 API를 사용하면 다음 작업을 할 수 있습니다.
부속 기기가 범위 내에 있을 때 앱의 절전 모드를 해제합니다.
자세한 내용은
CompanionDeviceManager.startObservingDevicePresence()
를 참고하세요.호환 기기가 범위 내에 유지되는 한 앱 프로세스가 계속 실행되도록 보장합니다.
자세한 내용은
CompanionDeviceService.onDeviceAppeared()
를 참고하세요.