Multi-Display Communications API

AAOS में सिस्टम के खास अधिकारों वाला ऐप्लिकेशन, Multi-Display Communications API का इस्तेमाल कर सकता है. इससे वह कार के किसी दूसरे ऑक्यूपंट ज़ोन में चल रहे उसी ऐप्लिकेशन (एक ही पैकेज का नाम) से कम्यूनिकेट कर सकता है. इस पेज पर, एपीआई को इंटिग्रेट करने का तरीका बताया गया है. ज़्यादा जानने के लिए, CarOccupantZoneManager.OccupantZoneInfo भी देखें.

ऑक्यूपेंट ज़ोन

ऑक्यूपेंट ज़ोन के कॉन्सेप्ट के तहत, किसी उपयोगकर्ता को डिसप्ले के सेट से मैप किया जाता है. हर ऑक्यूपेंट ज़ोन में, DISPLAY_TYPE_MAIN टाइप का डिसप्ले होता है. किसी ज़ोन में, क्लस्टर डिसप्ले जैसे अन्य डिसप्ले भी हो सकते हैं. हर ऑक्यूपेंट ज़ोन को एक Android उपयोगकर्ता असाइन किया जाता है. हर उपयोगकर्ता के पास अपने खाते और ऐप्लिकेशन होते हैं.

हार्डवेयर कॉन्फ़िगरेशन

Comms API सिर्फ़ एक SoC के साथ काम करता है. सिंगल SoC मॉडल में, सभी ऑक्यूपेंट ज़ोन और उपयोगकर्ता एक ही SoC पर काम करते हैं. Comms API में तीन कॉम्पोनेंट होते हैं:

  • पावर मैनेजमेंट एपीआई की मदद से क्लाइंट, ऑक्यूपंट ज़ोन में मौजूद डिसप्ले की पावर को मैनेज कर सकता है.

  • Discovery API की मदद से क्लाइंट, कार के दूसरे ऑक्यूपंट ज़ोन की स्थितियों को मॉनिटर कर सकता है. साथ ही, उन ऑक्यूपंट ज़ोन में मौजूद मिलते-जुलते क्लाइंट को मॉनिटर कर सकता है. Connection API का इस्तेमाल करने से पहले, Discovery API का इस्तेमाल करें.

  • Connection API की मदद से, क्लाइंट किसी दूसरे ज़ोन में मौजूद अपने जैसे क्लाइंट से कनेक्ट हो सकता है. साथ ही, वह अपने जैसे क्लाइंट को पेलोड भेज सकता है.

कनेक्शन के लिए, Discovery API और Connection API ज़रूरी हैं. पावर मैनेजमेंट एपीआई का इस्तेमाल करना ज़रूरी नहीं है.

Comms API, अलग-अलग ऐप्लिकेशन के बीच कम्यूनिकेशन की सुविधा नहीं देता. इसके बजाय, इसे एक ही पैकेज के नाम वाले ऐप्लिकेशन के बीच कम्यूनिकेशन के लिए सिर्फ़ डिज़ाइन किया गया है. साथ ही, इसका इस्तेमाल सिर्फ़ उन उपयोगकर्ताओं के बीच कम्यूनिकेशन के लिए किया जाता है जो दिखते हैं.

इंटिग्रेशन गाइड

AbstractReceiverService को लागू करना

Payload पाने के लिए, रिसीवर ऐप्लिकेशन को AbstractReceiverService में तय किए गए ऐब्स्ट्रैक्ट तरीके लागू करने होंगे. उदाहरण के लिए:

public class MyReceiverService extends AbstractReceiverService {

    @Override
    public void onConnectionInitiated(@NonNull OccupantZoneInfo senderZone) {
    }

    @Override
    public void onPayloadReceived(@NonNull OccupantZoneInfo senderZone,
            @NonNull Payload payload) {
    }
}

onConnectionInitiated() को तब शुरू किया जाता है, जब भेजने वाला क्लाइंट, पाने वाले इस क्लाइंट से कनेक्शन का अनुरोध करता है. अगर कनेक्शन बनाने के लिए उपयोगकर्ता की पुष्टि ज़रूरी है, तो MyReceiverService can इस तरीके को बदलकर, अनुमति से जुड़ी गतिविधि लॉन्च कर सकता है. साथ ही, नतीजे के आधार पर acceptConnection() या rejectConnection() को कॉल कर सकता है. इसके अलावा, MyReceiverService सिर्फ़ acceptConnection() को कॉल कर सकता है.

onPayloadReceived() तब शुरू होता है, जब MyReceiverService को भेजने वाले क्लाइंट से Payload मिलता है. MyReceiverService इस तरीके को बदल सकता है, ताकि:

  • अगर कोई है, तो Payload को उससे जुड़े रिसीवर एंडपॉइंट पर फ़ॉरवर्ड करें. रजिस्टर किए गए रिसीवर एंडपॉइंट पाने के लिए, getAllReceiverEndpoints() को कॉल करें. किसी दिए गए रिसीवर एंडपॉइंट पर Payload को फ़ॉरवर्ड करने के लिए, forwardPayload() को कॉल करें

या,

  • Payload को कैश मेमोरी में सेव करें और जब उम्मीद के मुताबिक रिसीवर एंडपॉइंट रजिस्टर हो जाए, तब उसे भेजें. इसके लिए, MyReceiverService को onReceiverRegistered() के ज़रिए सूचना दी जाती है

AbstractReceiverService का एलान करें

रिसीवर ऐप्लिकेशन को अपनी मेनिफ़ेस्ट फ़ाइल में, लागू किए गए AbstractReceiverService की जानकारी देनी होगी. साथ ही, इस सेवा के लिए कार्रवाई android.car.intent.action.RECEIVER_SERVICE के साथ एक इंटेंट फ़िल्टर जोड़ना होगा. इसके अलावा, android.car.occupantconnection.permission.BIND_RECEIVER_SERVICE अनुमति की ज़रूरत होगी:

<service android:name=".MyReceiverService"
         android:permission="android.car.occupantconnection.permission.BIND_RECEIVER_SERVICE"
         android:exported="true">
    <intent-filter>
        <action android:name="android.car.intent.action.RECEIVER_SERVICE" />
    </intent-filter>
</service>

android.car.occupantconnection.permission.BIND_RECEIVER_SERVICEअनुमति से यह पक्का होता है कि सिर्फ़ फ़्रेमवर्क इस सेवा से जुड़ सकता है. अगर इस सेवा के लिए अनुमति की ज़रूरत नहीं है, तो कोई दूसरा ऐप्लिकेशन इस सेवा से जुड़ सकता है और सीधे तौर पर Payload भेज सकता है.

अनुमति के बारे में जानकारी देना

क्लाइंट ऐप्लिकेशन को अपनी मेनिफ़ेस्ट फ़ाइल में अनुमतियों का एलान करना होगा.

<!-- This permission is needed for connection API -->
<uses-permission android:name="android.car.permission.MANAGE_OCCUPANT_CONNECTION"/>
<!-- This permission is needed for discovery API -->
<uses-permission android:name="android.car.permission.MANAGE_REMOTE_DEVICE"/>
<!-- This permission is needed if the client app calls CarRemoteDeviceManager#setOccupantZonePower() -->
<uses-permission android:name="android.car.permission.CAR_POWER"/>

ऊपर दी गई तीनों अनुमतियां, खास अनुमतियां हैं. इन्हें अनुमति वाली सूची में शामिल फ़ाइलों के ज़रिए पहले से मंज़ूरी मिलनी चाहिए. उदाहरण के लिए, यहां MultiDisplayTest ऐप्लिकेशन की अनुमति वाली सूची की फ़ाइल दी गई है:

// packages/services/Car/data/etc/com.google.android.car.multidisplaytest.xml
<permissions>
    <privapp-permissions package="com.google.android.car.multidisplaytest">
         
        <permission name="android.car.permission.MANAGE_OCCUPANT_CONNECTION"/>
        <permission name="android.car.permission.MANAGE_REMOTE_DEVICE"/>
        <permission name="android.car.permission.CAR_POWER"/>
    </privapp-permissions>
</permissions>

कार के मैनेजर पाना

एपीआई का इस्तेमाल करने के लिए, क्लाइंट ऐप्लिकेशन को CarServiceLifecycleListener रजिस्टर करना होगा, ताकि इससे जुड़े कार मैनेजर मिल सकें:

private CarRemoteDeviceManager mRemoteDeviceManager;
private CarOccupantConnectionManager mOccupantConnectionManager;

private final Car.CarServiceLifecycleListener mCarServiceLifecycleListener = (car, ready) -> {
   if (!ready) {
       Log.w(TAG, "Car service crashed");
       mRemoteDeviceManager = null;
       mOccupantConnectionManager = null;
       return;
   }
   mRemoteDeviceManager = car.getCarManager(CarRemoteDeviceManager.class);
   mOccupantConnectionManager = car.getCarManager(CarOccupantConnectionManager.class);
};

Car.createCar(getContext(), /* handler= */ null, Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER,
       mCarServiceLifecycleListener);

(भेजने वाला) डिस्कवर

भेजने वाले क्लाइंट को, पाने वाले क्लाइंट से कनेक्ट करने से पहले, CarRemoteDeviceManager.StateCallback रजिस्टर करके, पाने वाले क्लाइंट का पता लगाना चाहिए:

// The maps are accessed by the main thread only, so there is no multi-thread issue.
private final ArrayMap<OccupantZoneInfo, Integer> mOccupantZoneStateMap = new ArrayMap<>();
private final ArrayMap<OccupantZoneInfo, Integer> mAppStateMap = new ArrayMap<>();

private final StateCallback mStateCallback = new StateCallback() {
        @Override
        public void onOccupantZoneStateChanged(
                @androidx.annotation.NonNull OccupantZoneInfo occupantZone,
                int occupantZoneStates) {
            mOccupantZoneStateMap.put(occupantZone, occupantZoneStates);
        }
        @Override
        public void onAppStateChanged(
                @androidx.annotation.NonNull OccupantZoneInfo occupantZone,
                int appStates) {
            mAppStateMap.put(occupantZone, appStates);
        }
    };

if (mRemoteDeviceManager != null) {
   mRemoteDeviceManager.registerStateCallback(getActivity().getMainExecutor(),
           mStateCallback);
}

कास्ट किए जाने वाले डिवाइस से कनेक्ट करने का अनुरोध करने से पहले, भेजने वाले को यह पक्का करना चाहिए कि कास्ट किए जाने वाले डिवाइस के उपयोगकर्ता ज़ोन और कास्ट किए जाने वाले डिवाइस के ऐप्लिकेशन के सभी फ़्लैग सेट हों. ऐसा न करने पर, गड़बड़ियां हो सकती हैं. उदाहरण के लिए:

private boolean canRequestConnectionToReceiver(OccupantZoneInfo receiverZone) {
    Integer zoneState = mOccupantZoneStateMap.get(receiverZone);
    if ((zoneState == null) || (zoneState.intValue() & (FLAG_OCCUPANT_ZONE_POWER_ON
            // FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED is not implemented yet. Right now
            // just ignore this flag.
            //  | FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED
            | FLAG_OCCUPANT_ZONE_CONNECTION_READY)) == 0) {
        return false;
    }
    Integer appState = mAppStateMap.get(receiverZone);
    if ((appState == null) ||
        (appState.intValue() & (FLAG_CLIENT_INSTALLED
            | FLAG_CLIENT_SAME_LONG_VERSION | FLAG_CLIENT_SAME_SIGNATURE
            | FLAG_CLIENT_RUNNING | FLAG_CLIENT_IN_FOREGROUND)) == 0) {
        return false;
    }
    return true;
}

हमारा सुझाव है कि ईमेल भेजने वाला व्यक्ति, ईमेल पाने वाले व्यक्ति से कनेक्शन का अनुरोध तब ही करे, जब ईमेल पाने वाले व्यक्ति के सभी फ़्लैग सेट हों. हालांकि, इसके कुछ अपवाद हैं:

  • कनेक्शन बनाने के लिए, FLAG_OCCUPANT_ZONE_CONNECTION_READY और FLAG_CLIENT_INSTALLED का होना ज़रूरी है.

  • अगर रिसीवर ऐप्लिकेशन को कनेक्शन के लिए उपयोगकर्ता की मंज़ूरी पाने के लिए यूज़र इंटरफ़ेस (यूआई) दिखाना है, तो FLAG_OCCUPANT_ZONE_POWER_ON और FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED अतिरिक्त ज़रूरी शर्तें बन जाती हैं. बेहतर उपयोगकर्ता अनुभव के लिए, FLAG_CLIENT_RUNNING और FLAG_CLIENT_IN_FOREGROUND का इस्तेमाल करने का भी सुझाव दिया जाता है. ऐसा न करने पर, उपयोगकर्ता को हैरानी हो सकती है.

  • फ़िलहाल, Android 15 के लिए FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED लागू नहीं किया गया है. क्लाइंट ऐप्लिकेशन इसे अनदेखा कर सकता है.

  • फ़िलहाल (Android 15), Comms API सिर्फ़ एक ही Android इंस्टेंस पर कई उपयोगकर्ताओं के साथ काम करता है, ताकि पीयर ऐप्लिकेशन में एक ही लॉन्ग वर्शन कोड (FLAG_CLIENT_SAME_LONG_VERSION) और हस्ताक्षर (FLAG_CLIENT_SAME_SIGNATURE) हो. इस वजह से, ऐप्लिकेशन को यह पुष्टि करने की ज़रूरत नहीं होती कि दोनों वैल्यू एक जैसी हैं.

उपयोगकर्ता को बेहतर अनुभव देने के लिए, अगर कोई फ़्लैग सेट नहीं किया गया है, तो ईमेल भेजने वाला क्लाइंट यूज़र इंटरफ़ेस (यूआई) दिखा सकता है. उदाहरण के लिए, अगर FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED सेट नहीं है, तो भेजने वाला व्यक्ति, उपयोगकर्ता को सूचना या डायलॉग दिखा सकता है. इससे उपयोगकर्ता को, पाने वाले व्यक्ति के ऑक्यूपेंट ज़ोन की स्क्रीन को अनलॉक करने के लिए कहा जा सकता है.

जब भेजने वाले को अब पाने वालों का पता लगाने की ज़रूरत नहीं होती है (उदाहरण के लिए, जब उसे सभी पाने वाले मिल जाते हैं और कनेक्शन बन जाते हैं या जब वह निष्क्रिय हो जाता है), तो वह खोज को बंद कर सकता है.

if (mRemoteDeviceManager != null) {
    mRemoteDeviceManager.unregisterStateCallback();
}

डिवाइस ढूंढने की सुविधा बंद होने पर, मौजूदा कनेक्शन पर कोई असर नहीं पड़ता. मैसेज भेजने वाला व्यक्ति, कनेक्ट किए गए डिवाइसों पर Payload भेज सकता है.

(भेजने वाला) कनेक्शन का अनुरोध करें

जब रिसीवर के सभी फ़्लैग सेट हो जाते हैं, तो भेजने वाला व्यक्ति, रिसीवर से कनेक्ट करने का अनुरोध कर सकता है:

    private final ConnectionRequestCallback mRequestCallback = new ConnectionRequestCallback() {
        @Override
        public void onConnected(OccupantZoneInfo receiverZone) {
        }

        @Override
        public void onFailed(OccupantZoneInfo receiverZone, int connectionError) {
        }

        @Override
        public void onDisconnected(OccupantZoneInfo receiverZone) {
        }
    };

if (mOccupantConnectionManager != null && canRequestConnectionToReceiver(receiverZone)) {
    mOccupantConnectionManager.requestConnection(receiverZone,
                getActivity().getMainExecutor(), mRequestCallback);
}

(रिसीवर सेवा) कनेक्शन स्वीकार करना

जब भेजने वाला व्यक्ति, पाने वाले व्यक्ति से कनेक्ट करने का अनुरोध करता है, तब पाने वाले व्यक्ति के ऐप्लिकेशन में मौजूद AbstractReceiverService, कार सेवा से जुड़ जाएगा. इसके बाद, AbstractReceiverService.onConnectionInitiated() को चालू किया जाएगा. (सेंडर) कनेक्शन का अनुरोध करें में बताए गए तरीके के मुताबिक, onConnectionInitiated() एक ऐब्स्ट्रैक्ट तरीका है. इसे क्लाइंट ऐप्लिकेशन को लागू करना होगा.

जब डिवाइस इस्तेमाल करने वाला व्यक्ति, कनेक्शन का अनुरोध स्वीकार करता है, तो भेजने वाले व्यक्ति के ConnectionRequestCallback.onConnected() को चालू किया जाता है. इसके बाद, कनेक्शन बन जाता है.

(भेजने वाला) पेलोड भेजता है

कनेक्शन बन जाने के बाद, भेजने वाला व्यक्ति, पाने वाले व्यक्ति को Payload भेज सकता है:

if (mOccupantConnectionManager != null) {
    Payload payload = ...;
    try {
        mOccupantConnectionManager.sendPayload(receiverZone, payload);
    } catch (CarOccupantConnectionManager.PayloadTransferException e) {
        Log.e(TAG, "Failed to send Payload to " + receiverZone);
    }
}

भेजने वाला व्यक्ति, Payload में Binder ऑब्जेक्ट या बाइट ऐरे रख सकता है. अगर भेजने वाले को अन्य डेटा टाइप भेजने हैं, तो उसे डेटा को बाइट ऐरे में क्रम से लगाना होगा. साथ ही, बाइट ऐरे का इस्तेमाल करके Payload ऑब्जेक्ट बनाना होगा और Payload भेजना होगा. इसके बाद, रिसीवर क्लाइंट को मिले Payload से बाइट ऐरे मिलता है. साथ ही, वह बाइट ऐरे को उम्मीद के मुताबिक डेटा ऑब्जेक्ट में बदलता है. उदाहरण के लिए, अगर भेजने वाले व्यक्ति को आईडी FragmentB वाले पाने वाले व्यक्ति के एंडपॉइंट को स्ट्रिंग hello भेजनी है, तो वह डेटा टाइप को इस तरह से तय करने के लिए, Proto Buffers का इस्तेमाल कर सकता है:

message MyData {
  required string receiver_endpoint_id = 1;
  required string data = 2;
}

पहली इमेज में, Payload फ़्लो दिखाया गया है:

पेलोड भेजना

पहली इमेज. पेलोड भेजें.

(Receiver service) Receive and dispatch the payload

जब रिसीवर ऐप्लिकेशन को Payload मिलता है, तब उसका AbstractReceiverService.onPayloadReceived() शुरू हो जाता है. पे लोड भेजना में बताए गए तरीके के मुताबिक, onPayloadReceived() एक ऐब्स्ट्रैक्टेड तरीका है. इसे क्लाइंट ऐप्लिकेशन को लागू करना होगा. इस तरीके में, क्लाइंट Payload को संबंधित रिसीवर एंडपॉइंट पर फ़ॉरवर्ड कर सकता है. इसके अलावा, Payload को कैश मेमोरी में सेव करके, रिसीवर एंडपॉइंट के रजिस्टर होने के बाद भेज सकता है.

(टैकल किए गए व्यक्ति का एंडपॉइंट) रजिस्टर और अनरजिस्टर करना

रिसीवर ऐप्लिकेशन को registerReceiver() को कॉल करके, रिसीवर एंडपॉइंट रजिस्टर करने चाहिए. इस्तेमाल का एक सामान्य उदाहरण यह है कि किसी फ़्रैगमेंट को Payload पाने की ज़रूरत होती है. इसलिए, वह एक रिसीवर एंडपॉइंट रजिस्टर करता है:

private final PayloadCallback mPayloadCallback = (senderZone, payload) -> {
    
};

if (mOccupantConnectionManager != null) {
    mOccupantConnectionManager.registerReceiver("FragmentB",
                getActivity().getMainExecutor(), mPayloadCallback);
}

जब पाने वाले व्यक्ति के क्लाइंट में मौजूद AbstractReceiverService, Payload को पाने वाले व्यक्ति के एंडपॉइंट पर भेज देता है, तब उससे जुड़ा PayloadCallback शुरू हो जाएगा.

क्लाइंट ऐप्लिकेशन, एक से ज़्यादा रिसीवर एंडपॉइंट रजिस्टर कर सकता है. हालांकि, यह ज़रूरी है कि क्लाइंट ऐप्लिकेशन में उनके receiverEndpointId यूनीक हों. receiverEndpointId का इस्तेमाल AbstractReceiverService करेगा, ताकि यह तय किया जा सके कि पेलोड को किस रिसीवर एंडपॉइंट पर भेजा जाए. उदाहरण के लिए:

  • भेजने वाला व्यक्ति, Payload में receiver_endpoint_id:FragmentB के बारे में बताता है. Payload मिलने पर, AbstractReceiverService में मौजूद forwardPayload("FragmentB", payload), पेलोड को FragmentB पर भेजने के लिए कॉल करता है
  • भेजने वाला व्यक्ति, Payload में data_type:VOLUME_CONTROL के बारे में बताता है. Payload मिलने पर, रिसीवर में मौजूद AbstractReceiverService को पता होता है कि इस तरह के Payload को FragmentB को भेजा जाना चाहिए. इसलिए, यह forwardPayload("FragmentB", payload) को कॉल करता है
if (mOccupantConnectionManager != null) {
    mOccupantConnectionManager.unregisterReceiver("FragmentB");
}

(भेजने वाला) कनेक्शन बंद करना

जब भेजने वाले को, पाने वाले को Payload भेजने की ज़रूरत नहीं होती (उदाहरण के लिए, जब यह बंद हो जाता है), तो उसे कनेक्शन बंद कर देना चाहिए.

if (mOccupantConnectionManager != null) {
    mOccupantConnectionManager.disconnect(receiverZone);
}

खाता अनलिंक करने के बाद, भेजने वाला व्यक्ति, पाने वाले व्यक्ति को Payload नहीं भेज पाएगा.

कनेक्शन फ़्लो

कनेक्शन फ़्लो को दूसरी इमेज में दिखाया गया है.

कनेक्शन फ़्लो

दूसरी इमेज. कनेक्शन फ़्लो.

समस्या का हल

लॉग देखें

इससे जुड़े लॉग देखने के लिए:

  1. लॉगिंग के लिए, यह निर्देश चलाएं:

    adb shell setprop log.tag.CarRemoteDeviceService VERBOSE && adb shell setprop log.tag.CarOccupantConnectionService VERBOSE && adb logcat -s "AbstractReceiverService","CarOccupantConnectionManager","CarRemoteDeviceManager","CarRemoteDeviceService","CarOccupantConnectionService"
  2. CarRemoteDeviceService और CarOccupantConnectionService की इंटरनल स्थिति को डंप करने के लिए:

    adb shell dumpsys car_service --services CarRemoteDeviceService && adb shell dumpsys car_service --services CarOccupantConnectionService

CarRemoteDeviceManager और CarOccupantConnectionManager को शून्य पर सेट करना

इन संभावित मुख्य वजहों को देखें:

  1. कार सेवा क्रैश हो गई. जैसा कि ऊपर बताया गया है, कार सेवा के क्रैश होने पर, दोनों मैनेजर को जान-बूझकर null पर रीसेट कर दिया जाता है. कार की सेवा को फिर से शुरू करने पर, दोनों मैनेजर को नॉन-नल वैल्यू पर सेट किया जाता है.

  2. CarRemoteDeviceService या CarOccupantConnectionService में से किसी एक को चालू नहीं किया गया है. यह पता लगाने के लिए कि इनमें से कोई एक सुविधा चालू है या नहीं, यह कमांड चलाएं:

    adb shell dumpsys car_service --services CarFeatureController
    • mDefaultEnabledFeaturesFromConfig ढूंढें. इसमें car_remote_device_service और car_occupant_connection_service शामिल होना चाहिए. उदाहरण के लिए:

      mDefaultEnabledFeaturesFromConfig:[car_evs_service, car_navigation_service, car_occupant_connection_service, car_remote_device_service, car_telemetry_service, cluster_home_service, com.android.car.user.CarUserNoticeService, diagnostic, storage_monitoring, vehicle_map_service]
      
    • डिफ़ॉल्ट रूप से, ये दोनों सेवाएं बंद होती हैं. अगर किसी डिवाइस पर एक से ज़्यादा डिसप्ले इस्तेमाल किए जा सकते हैं, तो आपको इस कॉन्फ़िगरेशन फ़ाइल को ओवरले करना होगा. कॉन्फ़िगरेशन फ़ाइल में, इन दोनों सेवाओं को चालू किया जा सकता है:

      // packages/services/Car/service/res/values/config.xml
      <string-array translatable="false" name="config_allowed_optional_car_features">
           <item>car_occupant_connection_service</item>
           <item>car_remote_device_service</item>
            
      </string-array>
      

एपीआई को कॉल करते समय अपवाद

अगर क्लाइंट ऐप्लिकेशन, एपीआई का इस्तेमाल सही तरीके से नहीं कर रहा है, तो अपवाद हो सकता है. इस मामले में, क्लाइंट ऐप्लिकेशन, समस्या को ठीक करने के लिए अपवाद और क्रैश स्टैक में मौजूद मैसेज की जांच कर सकता है. एपीआई के गलत इस्तेमाल के उदाहरण:

  • registerStateCallback() इस क्लाइंट ने पहले ही StateCallback रजिस्टर कर लिया है.
  • unregisterStateCallback() इस CarRemoteDeviceManager इंस्टेंस ने किसी StateCallback को रजिस्टर नहीं किया है.
  • registerReceiver() receiverEndpointId पहले से ही रजिस्टर है.
  • unregisterReceiver() receiverEndpointId रजिस्टर नहीं है.
  • requestConnection() कोई कनेक्शन पहले से मौजूद है या कनेक्शन बनाने का अनुरोध किया गया है.
  • cancelConnection() कनेक्शन का कोई अनुरोध लंबित नहीं है जिसे रद्द किया जा सके.
  • sendPayload() कोई कनेक्शन नहीं है.
  • disconnect() कोई कनेक्शन नहीं है.

Client1, Client2 को पेलोड भेज सकता है, लेकिन Client2, Client1 को पेलोड नहीं भेज सकता

कनेक्शन को इस तरह से डिज़ाइन किया गया है कि यह सिर्फ़ एक ही दिशा में काम करता है. दोनों खातों के बीच कनेक्शन बनाने के लिए, client1 और client2, दोनों को एक-दूसरे से कनेक्ट करने का अनुरोध करना होगा. इसके बाद, उन्हें अनुमति लेनी होगी.