إقران الأجهزة المصاحبة

على الأجهزة التي تعمل بالإصدار 8.0 من نظام التشغيل Android (المستوى 26 لواجهة برمجة التطبيقات) والإصدارات الأحدث، يُجري إقران الجهاز المصاحب فحصًا عبر البلوتوث أو Wi-Fi للأجهزة المجاورة نيابةً عن تطبيقك بدون الحاجة إلى إذن ACCESS_FINE_LOCATION. يساعد ذلك في زيادة حماية خصوصية المستخدمين إلى أقصى حدّ. بعد إقران الجهاز، يمكن له الاستفادة من إذنَي REQUEST_COMPANION_RUN_IN_BACKGROUND وREQUEST_COMPANION_USE_DATA_IN_BACKGROUND لتشغيل التطبيق من الخلفية. استخدِم هذه الطريقة لإجراء التهيئة الأولية للجهاز المصاحب، مثل الساعة الذكية المتوافقة مع تقنية BLE. علاوةً على ذلك، يتطلب إقران الأجهزة المصاحبة تفعيل "خدمات الموقع الجغرافي".

لا يؤدي إقران الجهاز المصاحب إلى إنشاء اتصالات من تلقاء نفسه. تُنشئ واجهات برمجة التطبيقات لاتصال البلوتوث وWi-Fi الاتصالات. ولا يؤدي إقران الأجهزة المصاحبة إلى تمكين المسح المستمر.

يمكن للمستخدم اختيار جهاز من قائمة ومنحه الإذن بالوصول إلى أحد التطبيقات. يتم إبطال هذه الأذونات في حال إلغاء تثبيت التطبيق أو طلب الرقم disassociate(). ويكون التطبيق مسؤولاً عن محو عمليات الربط الخاصة به إذا لم يعُد المستخدم بحاجة إليها، على سبيل المثال عند تسجيل الخروج أو إزالة الأجهزة المرتبطة.

تنفيذ إقران الأجهزة المصاحبة

لإنشاء اتصال بجهاز مصاحب وإدارته، استخدِم CompanionDeviceManager. يوضّح هذا القسم كيفية تخصيص مربّع حوار طلب الإقران عند إقران تطبيقك بالأجهزة المصاحبة عبر البلوتوث وتقنية البلوتوث منخفض الطاقة (BLE) وWi-Fi.

تحديد الأجهزة المصاحبة

يوضح نموذج الرمز البرمجي التالي كيفية إضافة العلامة <uses-feature> إلى ملف البيان. يُعلم هذا النظام أن تطبيقك يهدف إلى إعداد الأجهزة المصاحبة.

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

إدراج الأجهزة حسب النوع

يمكنك عرض جميع الأجهزة المصاحبة المحتملة التي تتطابق مع الفلتر الذي تقدمه، أو حصر الشاشة بخيار واحد (كما هو موضح في الشكل 1). يمكنك ضبط ذلك من خلال إنشاء فلتر يحدّد أنواع الأجهزة التي يبحث عنها تطبيقك أو من خلال ضبط setSingleDevice() على true (كما هو موضّح في الشكل 2).

شاشة إقران الجهاز المصاحب، تقتصر على خيار إقران واحد.
الشكل 1. شاشة إقران الجهاز المصاحب، تقتصر على خيار إقران واحد.
شاشة &quot;إقران الجهاز المصاحب&quot;، تقتصر على خيار إقران واحد بدون ملف شخصي.
الشكل 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، شغِّل الدالة associate() على CompanionDeviceManager. تستخدم الدالة associate() إقران كائن الطلب ورد الاتصال. تشير عمليات معاودة الاتصال عندما يحدد أحد التطبيقات موقع جهازه ويكون جاهزًا لتشغيل مربع حوار للمستخدم لإدخال اختياره. إذا لم يعثر التطبيق على أي أجهزة، ستعرض عملية معاودة الاتصال رسالة خطأ.

على الأجهزة التي تعمل بنظام التشغيل Android 13 (المستوى 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 (المستوى 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);

لتمكين المستخدمين من اختيار نوع الأجهزة التي يريدون الاتصال بها، يمكنك بدء نشاط الإعدادات المفضّلة باستخدام المَعلمة intentSender في الدالة onAssociationPending(). يتم إرسال نتائج هذا الإجراء مرة أخرى إلى الجزء في وظيفة onActivityResult() في نشاط إعداداتك المفضّلة. ويحدثك ذلك عندما يُجري المستخدم اختيارًا استنادًا إلى النتيجة. ويمكنك بعد ذلك الوصول إلى الجهاز الذي تم اختياره. عندما يختار المستخدم جهازًا يتضمّن بلوتوث، تكون النتيجة المرسَلة هي عنصر BluetoothDevice. وبالمثل، عندما ترصد الدالة onAssociationPending() أنّ المستخدم يختار جهاز Bluetooth 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 (المستوى 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 (المستوى 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 (المستوى 31 لواجهة برمجة التطبيقات) والإصدارات الأحدث استخدام الملفات الشخصية للأجهزة المصاحبة عند الاتصال بساعة. لمزيد من المعلومات، يمكنك الاطّلاع على دليل طلب الأذونات على Wear OS.

إبقاء التطبيقات المصاحبة نشطة

في نظام التشغيل Android 12 (المستوى 31 لواجهة برمجة التطبيقات) والإصدارات الأحدث، يمكنك استخدام واجهات برمجة تطبيقات إضافية للمساعدة في مواصلة تشغيل تطبيقك المصاحب عندما يكون الجهاز المصاحب ضمن النطاق. تتيح لك واجهات برمجة التطبيقات هذه القيام بما يلي: