Çoklu Ekran İletişim API'sı

Multi-Display Communications API, Farklı bir uygulamada çalışan aynı uygulamayla (aynı paket adı) iletişim kurmak için AAOS yolcu bölgesi. Bu sayfada, API'nin nasıl entegre edileceği açıklanmaktadır. Öğrenmek için diğer seçeneklerle birlikte CarOccupantZoneManager.OccupantZoneInfo.

Konuk bölgesi

Yolcu bölgesi kavramı, kullanıcıyı bir dizi ekranla eşleştirir. Her biri yolcu bölgesinde DISPLAY_TYPE_MAIN. Yolcu bölgesinde, küme ekranı gibi ek ekranlar da bulunabilir. Her bir yolcu bölgesine bir Android kullanıcısı atanır. Her kullanıcının kendi hesabı var ve uygulamalar.

Donanım yapılandırması

Comms API yalnızca tek bir SoC'yi destekler. Tek SoC modelinde tüm konuklar ve alt bölgelerin ve kullanıcıların aynı çip üzerinde çalışması gerekir. Comms API üç bileşenden oluşur:

  • Power management API, istemcinin yolcu bölgelerinde gösterilir.

  • Discovery API, istemcinin diğer yolcuların durumunu izlemesine olanak tanır. ayarlayabilir ve bu yolcu bölgelerindeki benzer müşterileri izleyebilirsiniz. Tekliflerinizi otomatikleştirmek ve optimize etmek için Connection API'yi kullanmadan önce Discovery API'yi inceleyin.

  • Connection API, istemcinin eş istemcisine ve eş müşteriye yük göndermek için kullanılır.

Bağlantı için Discovery API ve Connection API gerekir. Güç management API isteğe bağlıdır.

Comms API, farklı uygulamalar arasında iletişimi desteklemez. Bunun yerine yalnızca aynı paket adına sahip uygulamalar arasındaki iletişim için tasarlanmıştır ve yalnızca görünür olan farklı kullanıcılar arasındaki iletişim için kullanılır.

Entegrasyon kılavuzu

AbstractReceiverService Uygulamasını Uygulama

Alıcı uygulamanın Payload almak için soyut yöntemleri uygulaması GEREKİR AbstractReceiverService içinde tanımlanmıştır. Örnek:

public class MyReceiverService extends AbstractReceiverService {

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

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

Gönderen istemci bir istek yaptığında onConnectionInitiated() çağrılır: bağlantı kuruluyor. Doğrulama için kullanıcı onayı gerekiyorsa MyReceiverService bu yöntemi geçersiz kılarak ve izin etkinliği için acceptConnection() veya rejectConnection() numaralı telefonu arayın yardımcı olur. Aksi takdirde, MyReceiverService yalnızca telefonla arama yapabilir acceptConnection().

onPayloadReceived()is invoked whenMyReceiverServicehas received aYükfrom the sender client.MyReceiverService bunu geçersiz kılabilir yöntemini kullanarak:

  • Varsa Payload öğesini ilgili alıcı uç noktalarına yönlendirin. Alıcı: kayıtlı alıcı uç noktalarını al, getAllReceiverEndpoints() çağrısı yap. Alıcı: Payload öğesini belirli bir alıcı uç noktasına yönlendirin, forwardPayload() çağrısı yapın

VEYA

  • Payload öğesini önbelleğe alın ve beklenen alıcı uç noktası şu olduğunda gönderin MyReceiverService için bildirim gönderilir. onReceiverRegistered()

AbstractReceiverService Bildirimi

Alıcı uygulamanın, uygulanan AbstractReceiverService öğesini kendi manifest dosyası, işlem içeren bir intent filtresi ekleyin Bu hizmet için android.car.intent.action.RECEIVER_SERVICE ve şunu gerektirir: android.car.occupantconnection.permission.BIND_RECEIVER_SERVICE izni:

<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İzni Bu hizmete yalnızca çerçevenin bağlanmasını sağlar. Bu hizmet başka bir uygulama buna bağlanabilir ve doğrudan ona bir Payload gönderin.

İzin beyan et

İstemci uygulamasının, manifest dosyasında izinleri beyan etmesi ZORUNLUDUR.

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

Yukarıdaki üç izinden her biri ayrıcalıklı izinler olup izin verilenler listesindeki dosyalar tarafından önceden izin verilir. Örneğin, şunun izin verilenler listesi dosyası: MultiDisplayTest uygulama:

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

Araç yöneticileri edinme

API'yi kullanmak için istemci uygulamasının bir CarServiceLifecycleListener kaydettirmesi GEREKİR ilişkili Araba yöneticilerini al:

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

(Gönderen) Keşfet

Alıcı istemciye bağlanmadan önce, gönderen istemcinin Alıcı bir istemci (CarRemoteDeviceManager.StateCallback) kaydederek:

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

Alıcıyla bağlantı isteğinde bulunmadan önce gönderen, alıcının alıcı yolcu bölgesinin ve alıcı uygulamasının bayrakları ayarlanır. Aksi halde ortaya çıkabilir. Örnek:

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

Gönderenin, alıcıyla bağlantıya geçmek istemesi için alıcının bayrakları ayarlanır. Bununla birlikte, bazı istisnalar söz konusudur:

  • FLAG_OCCUPANT_ZONE_CONNECTION_READY ve FLAG_CLIENT_INSTALLED bir bağlantı kurmak için gereken minimum gereksinimleri tanımlayın.

  • Alıcı uygulamanın bağlantı, FLAG_OCCUPANT_ZONE_POWER_ON ve FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED ek şartlar haline geldi. Örneğin, daha iyi kullanıcı deneyimi, FLAG_CLIENT_RUNNING ve FLAG_CLIENT_IN_FOREGROUND önerilir. Aksi takdirde kullanıcı şaşıracaksınız.

  • Şu an için (Android 15) FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED uygulanmamaktadır. İstemci uygulaması bunu yoksayabilir.

  • Comms API şimdilik (Android 15) yalnızca aynı Benzer uygulamaların aynı uzun sürüm koduna sahip olabilmesi için Android örneği (FLAG_CLIENT_SAME_LONG_VERSION) ve imza (FLAG_CLIENT_SAME_SIGNATURE). Sonuç olarak, uygulamaların uyumlu olması gerekir.

Daha iyi bir kullanıcı deneyimi için, işaret olmadığında gönderen istemci ayarlandı. Örneğin, FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED ayarlanmazsa gönderen kullanıcıya bir ileti mesajı veya bir iletişim kutusu gösterilebilir. yolcu bölgesi.

Gönderenin artık alıcıları keşfetmesi gerekmediğinde (örneğin, tüm alıcıları ve kurulan bağlantıları bulursa veya devre dışı kalırsa) CANNOT TRANSLATE keşfi durdurabilir.

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

Keşif durdurulduğunda mevcut bağlantılar etkilenmez. Gönderen, Payload öğesini bağlı alıcılara göndermeye devam edin.

(Gönderen) Bağlantı iste

Alıcının tüm flag'leri ayarlandığında, gönderen bağlantı isteyebilirsiniz. alıcı:

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

(Alıcı hizmeti) Bağlantıyı kabul edin

Gönderen, alıcıyla bağlantı kurmak istediğinde Alıcı uygulamadaki AbstractReceiverService araba hizmetine bağlı olacak, ve AbstractReceiverService.onConnectionInitiated() çağrılır. Farklı (Gönderen) İsteği Bağlantısı bölümünde açıklandığı gibi onConnectionInitiated() soyut bir yöntemdir ve istemci uygulaması.

Alıcı, bağlantı isteğini kabul ettiğinde gönderenin ConnectionRequestCallback.onConnected() çağrılır, ardından bağlantı kurulduğundan emin olun.

(Gönderen) Yükü gönder

Bağlantı kurulduktan sonra, gönderen, Payload CANNOT TRANSLATE alıcı:

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

Gönderen, Payload içine bir Binder nesnesi veya bir bayt dizisi yerleştirebilir. Öğe Gönderenin başka veri türleri göndermesi ve verileri bir bayt olarak serileştirmesi ZORUNLUDUR dizisinden birini kullanıyorsanız, bir Payload nesnesi oluşturmak için bayt dizisini kullanın ve Payload Ardından alıcı istemci, alınan Payload ve bayt dizisini beklenen veri nesnesinde seri durumdan çıkarır. Örneğin, gönderen, alıcıya bir Dize hello göndermek isterse FragmentB kimliğine sahip uç nokta varsa bir veri türü tanımlamak için Proto Arabellekleri kullanabilir aşağıdaki gibidir:

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

Şekil 1'de Payload akışı gösterilmektedir:

Yükü gönderin

Şekil 1. Yükü gönderin.

(Alıcı hizmeti) Yükü alıp gönderin

Alıcı uygulama Payload bilgisini aldıktan sonra, AbstractReceiverService.onPayloadReceived() çağrılır. Şurada açıklandığı gibi: Yükü gönder, onPayloadReceived() bir soyutlanmış bir yöntemdir ve istemci uygulaması tarafından Uygulanması ZORUNLUDUR. Bu yöntemde istemci, Payload öğesini ilgili alıcı uç noktalarına yönlendirebilir veya Payload öğesini önbelleğe alın ve ardından beklenen alıcı uç noktası oluşturulduğunda kayıtlı.

(Alıcı uç noktası) Kaydolma ve kaydı iptal etme

Alıcı uygulama, alıcıyı kaydetmek için registerReceiver() numarasını çağırmalı uç noktalar. Tipik bir kullanım örneği, bir Parçanın Payload alıcısının gerekmesidir. Bir alıcı uç noktası kaydeder:

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

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

Alıcı istemcideki AbstractReceiverService öğesi, Alıcı uç noktasına Payload gönderilirse ilişkilendirilmiş PayloadCallback olur çağrılır.

İstemci uygulaması, birden fazla alıcı uç noktasını CANNOT TRANSLATE receiverEndpointId öğeleri, istemci uygulaması arasında benzersizdir. receiverEndpointId hangi alıcıya karar vermek için AbstractReceiverService tarafından kullanılacaktır uç noktaları bulunur. Örnek:

  • Gönderen, Payload içinde receiver_endpoint_id:FragmentB değerini belirtiyor. Zaman Payload, alıcı aramalarındaki AbstractReceiverService alınıyor Yükü göndermek için forwardPayload("FragmentB", payload) FragmentB
  • Gönderen, Payload içinde data_type:VOLUME_CONTROL değerini belirtiyor. Zaman Payload alındığında alıcıdaki AbstractReceiverService bunu bilir. bu tür Payload öğelerinin FragmentB numaralı telefona gönderilmesi gerektiğini söyler, bu nedenle forwardPayload("FragmentB", payload)
ziyaret edin.
if (mOccupantConnectionManager != null) {
    mOccupantConnectionManager.unregisterReceiver("FragmentB");
}

(Gönderen) Bağlantıyı sonlandırma

Gönderenin, alıcıya Payload öğesini artık göndermesi gerekmediğinde (örneğin, devre dışı kalırsa) bağlantının sonlandırılması GEREKİR.

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

Bağlantı kesildikten sonra gönderen, alıcıya Payload gönderemez.

Bağlantı akışı

Bir bağlantı akışı Şekil 2'de gösterilmektedir.

Bağlantı akışı

Şekil 2. Bağlantı akışı.

Sorun giderme

Günlükleri kontrol etme

İlgili günlükleri kontrol etmek için:

  1. Günlük kaydı için şu komutu çalıştırın:

    adb shell setprop log.tag.CarRemoteDeviceService VERBOSE && adb shell setprop log.tag.CarOccupantConnectionService VERBOSE && adb logcat -s "AbstractReceiverService","CarOccupantConnectionManager","CarRemoteDeviceManager","CarRemoteDeviceService","CarOccupantConnectionService"
    
  2. CarRemoteDeviceService ve CarOccupantConnectionService:

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

Null CarRemoteDeviceManager ve CarOccupantConnectionManager

Aşağıdaki olası temel nedenleri inceleyin:

  1. Araba servisi çöktü. Daha önce de belirtildiği gibi iki yönetici araba servisi arızalandığında kasıtlı olarak null olacak şekilde sıfırlanır. Araba servisi ne zaman yeniden başlatılırsa, iki yönetici boş olmayan değerlere ayarlanır.

  2. CarRemoteDeviceService veya CarOccupantConnectionService değildir etkin. İkisinden birinin etkin olup olmadığını belirlemek için şu komutu çalıştırın:

    adb shell dumpsys car_service --services CarFeatureController
    
    • Şunları içermesi gereken mDefaultEnabledFeaturesFromConfig alanını bulun car_remote_device_service ve car_occupant_connection_service. Örneğin, örnek:

      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]
      
    • Varsayılan olarak bu iki hizmet devre dışıdır. Cihaz şunları desteklediğinde: kullanıyorsanız, bu yapılandırma dosyası için yer paylaşımı GEREKİR. Etkinleştirebileceğiniz iki hizmet de ekleyebilirsiniz:

      // 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 çağrılırken istisna

İstemci uygulaması API'yi gerektiği gibi kullanmıyorsa bir istisna oluşabilir. Bu durumda, istemci uygulaması istisnadaki mesajı ve kilitlenme yığınını da kullanabilirsiniz. API'nin hatalı kullanımına ilişkin örnekler şunlardır:

  • registerStateCallback() Bu müşteri zaten StateCallback kaydettirdi.
  • unregisterStateCallback() Bu tarafından hiçbir StateCallback kaydedilmedi CarRemoteDeviceManager örneği.
  • registerReceiver() receiverEndpointId zaten kayıtlı.
  • unregisterReceiver() receiverEndpointId kayıtlı değil.
  • requestConnection() Beklemede veya kurulmuş bir bağlantı zaten var.
  • cancelConnection() İptal edilecek bekleyen bağlantı yok.
  • sendPayload() Kurulu bağlantı yok.
  • disconnect() Kurulu bağlantı yok.

Client1, Yük'ü client2'ye gönderebilir ancak bunun tersi yapılamaz

Bağlantı, tek yönlüdür. İki yönlü bağlantı kurmak için client1 ve client2 birbirleriyle bağlantı kurmalarını ZORUNLUDUR ve ardından onay almanız gerekir.