Multi-Display Communications API

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

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

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

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

Comms API सिर्फ़ एक SoC के साथ काम करता है. सिंगल SoC मॉडल में, सभी ऑक्यूपंट ज़ोन और उपयोगकर्ता एक ही SoC पर काम करते हैं. Comms 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 इस तरीके को ओवरराइड कर सकता है. ऐसा करके, वह अनुमति से जुड़ी गतिविधि लॉन्च कर सकता है. साथ ही, नतीजे के आधार पर 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>

Car मैनेजर पाना

एपीआई का इस्तेमाल करने के लिए, ज़रूरी है कि क्लाइंट ऐप्लिकेशन, उससे जुड़े Car मैनेजर पाने के लिए 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 के फ़्लो को दिखाया गया है:

पेलोड भेजना

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

(पाने वाले की सेवा) पेलोड पाना और भेजना

जब पाने वाले ऐप्लिकेशन को 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 यूनीक हों. AbstractReceiverService, receiverEndpointId का इस्तेमाल करके यह तय करेगा कि `Payload` को किन पाने वाले एंडपॉइंट पर भेजना है. उदाहरण के लिए:

  • भेजने वाला क्लाइंट, Payload में receiver_endpoint_id:FragmentB तय करता है. Payload पाने पर, पाने वाले क्लाइंट में मौजूद AbstractReceiverService, Payload को FragmentB पर भेजने के लिए, forwardPayload("FragmentB", payload) को कॉल करता है
  • भेजने वाला क्लाइंट, Payload में data_type:VOLUME_CONTROL तय करता है. पाने पर, पाने वाले क्लाइंट में मौजूद AbstractReceiverService को पता होता है कि इस तरह का Payload, FragmentB पर भेजा जाना चाहिए. इसलिए, वह forwardPayload("FragmentB", payload) को कॉल करता है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() कनेक्शन नहीं बना है.

क्लाइंट1, क्लाइंट2 को पेलोड भेज सकता है, लेकिन क्लाइंट2, क्लाइंट1 को पेलोड नहीं भेज सकता

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