Ghép nối thiết bị đồng hành

Trên các thiết bị chạy Android 8.0 (API cấp 26) trở lên, tính năng ghép nối thiết bị đồng hành sẽ thay mặt ứng dụng của bạn quét Bluetooth hoặc Wi-Fi cho các thiết bị ở gần mà không yêu cầu quyền ACCESS_FINE_LOCATION. Điều này giúp tối đa hoá khả năng bảo vệ quyền riêng tư của người dùng. Sau khi ghép nối thiết bị, thiết bị có thể tận dụng các quyền REQUEST_COMPANION_RUN_IN_BACKGROUNDREQUEST_COMPANION_USE_DATA_IN_BACKGROUND để khởi động ứng dụng ở chế độ nền. Hãy sử dụng phương thức này để thực hiện cấu hình ban đầu của thiết bị đồng hành, chẳng hạn như đồng hồ thông minh hỗ trợ BLE. Ngoài ra, tính năng ghép nối thiết bị đồng hành yêu cầu bật Dịch vụ vị trí.

Quá trình ghép nối thiết bị đồng hành không tự tạo kết nối. Các API kết nối Bluetooth và Wi-Fi sẽ thiết lập các kết nối. Tính năng ghép nối thiết bị đồng hành cũng không hỗ trợ tính năng quét liên tục.

Người dùng có thể chọn một thiết bị trong danh sách và cấp cho thiết bị đó quyền truy cập vào ứng dụng. Các quyền này sẽ bị thu hồi nếu bạn gỡ cài đặt ứng dụng hoặc gọi disassociate(). Một ứng dụng chịu trách nhiệm xoá các mối liên kết của chính ứng dụng đó nếu người dùng không cần nữa, chẳng hạn như khi họ đăng xuất hoặc xoá các thiết bị liên kết.

Triển khai tính năng ghép nối thiết bị đồng hành

Để tạo và quản lý kết nối với thiết bị đồng hành, hãy sử dụng CompanionDeviceManager. Phần này giải thích cách tuỳ chỉnh hộp thoại yêu cầu ghép nối khi bạn ghép nối ứng dụng với các thiết bị đồng hành qua Bluetooth, BLE và Wi-Fi.

Chỉ định thiết bị đồng hành

Mã mẫu sau đây cho biết cách thêm cờ <uses-feature> vào một tệp kê khai. Điều này cho hệ thống biết rằng ứng dụng của bạn dự định thiết lập các thiết bị đồng hành.

<uses-feature android:name="android.software.companion_device_setup"/>

Liệt kê thiết bị theo loại

Bạn có thể hiển thị tất cả thiết bị đồng hành có thể có sẵn phù hợp với bộ lọc mà bạn cung cấp hoặc giới hạn hiển thị ở một tuỳ chọn duy nhất (như trong hình 1). Bạn định cấu hình cấu hình này bằng cách tạo một bộ lọc chỉ định loại thiết bị mà ứng dụng của bạn đang tìm hoặc bằng cách đặt setSingleDevice() thành true (như trong hình 2).

Màn hình Ghép nối thiết bị đồng hành, giới hạn ở một tuỳ chọn ghép nối.
Hình 1. Màn hình Ghép nối thiết bị đồng hành, giới hạn ở một tuỳ chọn ghép nối.
Màn hình Ghép nối thiết bị đồng hành, giới hạn ở một tuỳ chọn ghép nối mà không có hồ sơ.
Hình 2. Màn hình Ghép nối thiết bị đồng hành, giới hạn ở một tuỳ chọn ghép nối mà không có hồ sơ.

Để áp dụng bộ lọc cho danh sách thiết bị đồng hành xuất hiện trong hộp thoại yêu cầu, hãy kiểm tra xem Bluetooth có đang bật không hoặc kiểm tra xem Wi-Fi có đang bật không). Sau khi bật kết nối, bạn có thể thêm DeviceFilter. Các lớp con sau đây của DeviceFilter chỉ định những loại thiết bị mà ứng dụng của bạn có thể liên kết dựa trên loại kết nối:

Cả 3 lớp con đều có trình tạo giúp tinh giản cấu hình bộ lọc. Trong ví dụ sau, một thiết bị quét tìm thiết bị Bluetooth có 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();

Đặt DeviceFilter thành AssociationRequest để trình quản lý thiết bị có thể xác định loại thiết bị cần tìm kiếm.

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();

Sau khi bạn khởi chạy AssociationRequest, hãy chạy hàm associate() trên CompanionDeviceManager. Hàm associate() nhận đối tượng yêu cầu ghép nối và lệnh gọi lại. Các lệnh gọi lại cho biết thời điểm ứng dụng tìm thấy một thiết bị và sẵn sàng khởi chạy hộp thoại để người dùng nhập lựa chọn của họ. Nếu ứng dụng không tìm thấy thiết bị nào, lệnh gọi lại sẽ trả về một thông báo lỗi.

Trên các thiết bị chạy Android 13 (API cấp 33) trở lên:

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.
    });

Trên các thiết bị chạy Android 12L (API cấp 32) trở xuống (không dùng nữa):

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);

Để cho phép người dùng chọn loại thiết bị mà họ muốn kết nối, hãy bắt đầu một hoạt động lựa chọn ưu tiên bằng tham số intentSender trong hàm onAssociationPending(). Kết quả của thao tác này được gửi trở lại mảnh trong hàm onActivityResult() của hoạt động lựa chọn ưu tiên. Thao tác này sẽ cập nhật thông tin cho bạn khi người dùng lựa chọn dựa trên kết quả. Sau đó, bạn có thể truy cập vào thiết bị đã chọn. Khi người dùng chọn một thiết bị Bluetooth, kết quả được gửi là một đối tượng BluetoothDevice. Tương tự, khi hàm onAssociationPending() phát hiện thấy người dùng chọn thiết bị Bluetooth LE, hãy mong đợi một đối tượng android.bluetooth.le.ScanResult. Đối với các thiết bị Wi-Fi, hãy mong đợi có một đối tượng 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);
    }
}

Để triển khai tính năng ghép nối thiết bị đồng hành với các bộ lọc có thể chỉ định thiết bị và liệt kê thiết bị theo loại, hãy xem các ví dụ sau:

Trên các thiết bị chạy Android 13 (API cấp 33) trở lên:

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);
        }
    }
}

Trên các thiết bị chạy Android 12L (API cấp 32) trở xuống (không dùng nữa):

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);
        }
    }
}

Hồ sơ thiết bị đồng hành

Các ứng dụng của đối tác trên Android 12 (API cấp độ 31) trở lên có thể sử dụng hồ sơ của thiết bị đồng hành đồng hành khi kết nối với đồng hồ. Để biết thêm thông tin, hãy xem hướng dẫn yêu cầu cấp quyền trên Wear OS.

Duy trì trạng thái bật của các ứng dụng đồng hành

Trên Android 12 (API cấp 31) trở lên, bạn có thể sử dụng các API bổ sung để giúp ứng dụng đồng hành luôn chạy trong khi thiết bị đồng hành nằm trong phạm vi. Các API này cho phép bạn làm những việc sau: