মাল্টি-ডিসপ্লে কমিউনিকেশন API

AAOS-এর একটি সিস্টেম প্রিভিলেজড অ্যাপ মাল্টি-ডিসপ্লে কমিউনিকেশনস এপিআই ব্যবহার করে গাড়ির ভিন্ন অকুপ্যান্ট জোনে চলমান একই অ্যাপের (একই প্যাকেজ নেম) সাথে যোগাযোগ করতে পারে। এই পৃষ্ঠায় এপিআই-টি কীভাবে ইন্টিগ্রেট করতে হয় তা বর্ণনা করা হয়েছে। আরও জানতে, আপনি CarOccupantZoneManager.OccupantZoneInfo- ও দেখতে পারেন।

দখলদার অঞ্চল

অকুপ্যান্ট জোন ধারণাটি একজন ব্যবহারকারীকে একাধিক ডিসপ্লের সাথে সংযুক্ত করে। প্রতিটি অকুপ্যান্ট জোনের DISPLAY_TYPE_MAIN টাইপের একটি ডিসপ্লে থাকে। একটি অকুপ্যান্ট জোনে ক্লাস্টার ডিসপ্লের মতো অতিরিক্ত ডিসপ্লেও থাকতে পারে। প্রতিটি অকুপ্যান্ট জোনের জন্য একজন অ্যান্ড্রয়েড ব্যবহারকারীকে নির্দিষ্ট করা থাকে। প্রত্যেক ব্যবহারকারীর নিজস্ব অ্যাকাউন্ট এবং অ্যাপ থাকে।

হার্ডওয়্যার কনফিগারেশন

কমস এপিআই শুধুমাত্র একটি এসওসি সমর্থন করে। একক এসওসি মডেলে, সমস্ত অকুপ্যান্ট জোন এবং ব্যবহারকারী একই এসওসি-তে চলে। কমস এপিআই তিনটি উপাদান নিয়ে গঠিত:

  • পাওয়ার ম্যানেজমেন্ট এপিআই ক্লায়েন্টকে অকুপ্যান্ট জোনগুলোতে থাকা ডিসপ্লেগুলোর পাওয়ার নিয়ন্ত্রণ করার সুযোগ দেয়।

  • ডিসকভারি এপিআই ক্লায়েন্টকে গাড়ির অন্যান্য যাত্রী অঞ্চলের অবস্থা এবং সেই যাত্রী অঞ্চলগুলিতে থাকা সমকক্ষ ক্লায়েন্টদের পর্যবেক্ষণ করার সুযোগ দেয়। কানেকশন এপিআই ব্যবহার করার আগে ডিসকভারি এপিআই ব্যবহার করুন।

  • কানেকশন এপিআই ক্লায়েন্টকে অন্য অকুপ্যান্ট জোনে থাকা তার পিয়ার ক্লায়েন্টের সাথে সংযোগ স্থাপন করতে এবং পিয়ার ক্লায়েন্টের কাছে একটি পেলোড পাঠাতে দেয়।

সংযোগের জন্য ডিসকভারি এপিআই এবং কানেকশন এপিআই প্রয়োজন। পাওয়ার ম্যানেজমেন্ট এপিআই ঐচ্ছিক।

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() কল করতে পারে

প্রেরক ক্লায়েন্টের কাছ থেকে MyReceiverService একটি Payload গ্রহণ করলে onPayloadReceived() কল করা হয়। MyReceiverService এই মেথডটিকে ওভাররাইড করতে পারে :

  • Payload সংশ্লিষ্ট রিসিভার এন্ডপয়েন্ট(গুলি)-তে (যদি থাকে) ফরোয়ার্ড করুন। নিবন্ধিত রিসিভার এন্ডপয়েন্টগুলি পেতে, getAllReceiverEndpoints() কল করুন। Payload একটি নির্দিষ্ট রিসিভার এন্ডপয়েন্টে ফরোয়ার্ড করতে, forwardPayload() কল করুন।

অথবা,

  • Payload ক্যাশ করে রাখুন এবং প্রত্যাশিত রিসিভার এন্ডপয়েন্টটি রেজিস্টার হলে তা পাঠিয়ে দিন, যার জন্য onReceiverRegistered() এর মাধ্যমে MyReceiverService অবহিত করা হয়।

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"/>

উপরের তিনটি অনুমতির প্রতিটিই বিশেষাধিকারপ্রাপ্ত অনুমতি, যা অবশ্যই allowlist ফাইলের মাধ্যমে আগে থেকে মঞ্জুর করতে হবে। উদাহরণস্বরূপ, এখানে MultiDisplayTest অ্যাপের allowlist ফাইলটি দেওয়া হলো:

// 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 হলো ন্যূনতম প্রয়োজনীয়তা।

  • যদি সংযোগের জন্য ব্যবহারকারীর অনুমোদন পেতে রিসিভার অ্যাপটিকে একটি UI প্রদর্শন করতে হয়, তাহলে FLAG_OCCUPANT_ZONE_POWER_ON এবং FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED অতিরিক্ত আবশ্যক হয়ে ওঠে। আরও ভালো ব্যবহারকারী অভিজ্ঞতার জন্য FLAG_CLIENT_RUNNING এবং FLAG_CLIENT_IN_FOREGROUND ব্যবহার করারও পরামর্শ দেওয়া হয়, অন্যথায় ব্যবহারকারী অবাক হতে পারেন।

  • আপাতত (অ্যান্ড্রয়েড ১৫)-এ FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED প্রয়োগ করা হয়নি। ক্লায়েন্ট অ্যাপ এটিকে উপেক্ষা করতে পারে।

  • আপাতত (অ্যান্ড্রয়েড ১৫), কমস এপিআই (Comms API) শুধুমাত্র একই অ্যান্ড্রয়েড ইনস্ট্যান্সে একাধিক ব্যবহারকারীকে সমর্থন করে, যাতে সমকক্ষ অ্যাপগুলোর একই লং ভার্সন কোড ( FLAG_CLIENT_SAME_LONG_VERSION ) এবং সিগনেচার ( FLAG_CLIENT_SAME_SIGNATURE ) থাকতে পারে। ফলে, অ্যাপগুলোকে এই দুটি মানের মিল যাচাই করার প্রয়োজন হয় না।

ব্যবহারকারীর উন্নত অভিজ্ঞতার জন্য, কোনো ফ্ল্যাগ সেট করা না থাকলে প্রেরক ক্লায়েন্ট একটি ইউজার ইন্টারফেস (UI) দেখাতে পারে। উদাহরণস্বরূপ, যদি 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 পাঠাতে চায়, তবে সে প্রোটো বাফার ব্যবহার করে এইভাবে একটি ডেটা টাইপ সংজ্ঞায়িত করতে পারে:

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

চিত্র ১-এ Payload প্রবাহ দেখানো হয়েছে:

পেলোড পাঠান

চিত্র ১. পেলোড প্রেরণ করুন।

(রিসিভার পরিষেবা) পেলোড গ্রহণ ও প্রেরণ করা

রিসিভার অ্যাপটি Payload গ্রহণ করার সাথে সাথে, এর AbstractReceiverService.onPayloadReceived() কল করা হবে। "পেলোড পাঠান" অংশে যেমন ব্যাখ্যা করা হয়েছে, onPayloadReceived() একটি অ্যাবস্ট্রাক্টেড মেথড এবং এটি ক্লায়েন্ট অ্যাপকে অবশ্যই ইমপ্লিমেন্ট করতে হবে। এই মেথডে, ক্লায়েন্ট Payload সংশ্লিষ্ট রিসিভার এন্ডপয়েন্ট(গুলি)-তে ফরোয়ার্ড করতে পারে, অথবা Payload ক্যাশ করে রেখে প্রত্যাশিত রিসিভার এন্ডপয়েন্টটি রেজিস্টার হয়ে গেলে তা পাঠাতে পারে।

(রিসিভার এন্ডপয়েন্ট) নিবন্ধন এবং অনিবন্ধন

রিসিভার অ্যাপটির উচিত রিসিভার এন্ডপয়েন্টগুলো রেজিস্টার করার জন্য registerReceiver() কল করা। এর একটি সাধারণ ব্যবহার হলো, যখন কোনো Fragment-এর Payload গ্রহণ করার প্রয়োজন হয়, তখন এটি একটি রিসিভার এন্ডপয়েন্ট রেজিস্টার করে:

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

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

রিসিভার ক্লায়েন্টের AbstractReceiverService যখন রিসিভার এন্ডপয়েন্টে Payload প্রেরণ করে, তখন সংশ্লিষ্ট PayloadCallback কল করা হবে।

ক্লায়েন্ট অ্যাপ একাধিক রিসিভার এন্ডপয়েন্ট রেজিস্টার করতে পারে, তবে শর্ত হলো ক্লায়েন্ট অ্যাপের মধ্যে তাদের receiverEndpointId গুলো অবশ্যই অনন্য হতে হবে। কোন রিসিভার এন্ডপয়েন্ট(গুলোতে) পেলোড প্রেরণ করা হবে, তা নির্ধারণ করতে AbstractReceiverService এই receiverEndpointId ব্যবহার করবে। উদাহরণস্বরূপ:

  • প্রেরক Payload receiver_endpoint_id:FragmentB উল্লেখ করে। Payload গ্রহণ করার পর, প্রাপকের AbstractReceiverService পেলোডটিকে FragmentB তে প্রেরণ করার জন্য forwardPayload("FragmentB", payload) কল করে।
  • প্রেরক 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

নাল কাররিমোটডিভাইসম্যানেজার এবং কারঅকুপ্যান্টকানেকশনম্যানেজার

এই সম্ভাব্য মূল কারণগুলো যাচাই করে দেখুন:

  1. গাড়ি পরিষেবাটি ক্র্যাশ করেছে। পূর্বে যেমন দেখানো হয়েছে, গাড়ি পরিষেবা ক্র্যাশ করলে দুটি ম্যানেজারকে ইচ্ছাকৃতভাবে নাল ( null করে দেওয়া হয়। গাড়ি পরিষেবাটি পুনরায় চালু করা হলে, দুটি ম্যানেজারকে নন-নাল (non-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>
      

এপিআই কল করার সময় ব্যতিক্রম ঘটেছে

যদি ক্লায়েন্ট অ্যাপটি এপিআই (API) উদ্দেশ্য অনুযায়ী ব্যবহার না করে, তাহলে একটি এক্সেপশন (exception) ঘটতে পারে। এক্ষেত্রে, সমস্যাটি সমাধান করার জন্য ক্লায়েন্ট অ্যাপটি এক্সেপশনের মেসেজ এবং ক্র্যাশ স্ট্যাক (crash stack) পরীক্ষা করতে পারে। এপিআই-এর অপব্যবহারের উদাহরণগুলো হলো:

  • registerStateCallback() এই ক্লায়েন্ট ইতিমধ্যেই একটি StateCallback রেজিস্টার করেছে।
  • unregisterStateCallback() এই CarRemoteDeviceManager ইনস্ট্যান্স দ্বারা কোনো StateCallback রেজিস্টার করা হয়নি।
  • registerReceiver() receiverEndpointId ইতিমধ্যে নিবন্ধিত আছে।
  • unregisterReceiver() receiverEndpointId নিবন্ধিত নয়।
  • requestConnection() একটি অপেক্ষমান বা প্রতিষ্ঠিত সংযোগ ইতিমধ্যেই বিদ্যমান।
  • cancelConnection() বাতিল করার জন্য কোনো অপেক্ষমান সংযোগ নেই।
  • sendPayload() কোনো সংযোগ স্থাপিত হয়নি।
  • disconnect() কোনো সংযোগ স্থাপিত হয়নি।

ক্লায়েন্ট ১ ক্লায়েন্ট ২-কে পেলোড পাঠাতে পারে, কিন্তু এর বিপরীতটা সম্ভব নয়।

সংযোগটি নকশা অনুযায়ী একমুখী। দ্বিমুখী সংযোগ স্থাপন করতে হলে, client1 এবং client2 উভয়কেই একে অপরের কাছে সংযোগের জন্য অনুরোধ করতে হবে এবং তারপর অনুমোদন নিতে হবে।