Harici cihazları kontrol etme

Android 11 ve sonraki sürümlerde, Hızlı Erişim Cihaz Kontrolleri özelliği, kullanıcıların varsayılan başlatıcıdan üç etkileşim içinde bir kullanıcı imkanıyla ışıklar, termostatlar ve kameralar gibi harici cihazları hızlı bir şekilde görüntülemesine ve kontrol etmesine olanak tanır. Cihaz OEM'i hangi başlatıcının kullanılacağını seçer. Google Home gibi cihaz toplayıcılar ve üçüncü taraf satıcı uygulamaları, bu alanda gösterilecek cihazlar sağlayabilir. Bu sayfada, cihaz kontrollerini bu alanda nasıl göstereceğiniz ve kontrol uygulamanıza nasıl bağlayacağınız açıklanmaktadır.

Şekil 1. Android kullanıcı arayüzündeki cihaz kontrol alanı.

Bu desteği eklemek için bir ControlsProviderService oluşturup beyan edin. Uygulamanızın desteklediği kontrolleri önceden tanımlanmış kontrol türlerine göre oluşturun ve ardından bu kontroller için yayıncılar oluşturun.

Kullanıcı arayüzü

Cihazlar, Cihaz denetimleri bölümünde şablon widget'lar olarak gösterilir. Aşağıdaki resimde gösterildiği gibi beş cihaz kontrol widget'ı kullanılabilir:

Cihaz kontrolleri için widget'ı açma/kapatma
Aç/Kapat
Kaydırma çubuğu widget'ıyla açma/kapatma
Kaydırma çubuğuyla açma/kapatma
Cihaz kontrolleri için aralık kaydırma çubuğu widget'ı
Aralık (açılıp kapatılamaz)
Durumsuz açma/kapatma kontrolü widget'ı
Durumsuz açma/kapatma düğmesi
Sıcaklık paneli widget'ı kapalı durumda
Sıcaklık paneli (kapalı)
Şekil 2. Şablonlu widget koleksiyonu.

Bir widget'a dokunup basılı tuttuğunuzda daha ayrıntılı kontrol için uygulamaya yönlendirilirsiniz. Her widget'taki simgeyi ve rengi özelleştirebilirsiniz ancak en iyi kullanıcı deneyimi için varsayılan set cihazla eşleşiyorsa varsayılan simgeyi ve rengi kullanın.

Sıcaklık paneli widget'ı açık durumda
3.şekil Sıcaklık paneli widget'ını açın.

Hizmeti oluşturun

Bu bölümde, ControlsProviderService öğesinin nasıl oluşturulacağı gösterilmektedir. Bu hizmet, Android sistem kullanıcı arayüzüne, uygulamanızın Android kullanıcı arayüzünün Cihaz denetimleri alanında gösterilmesi gereken cihaz denetimleri içerdiğini bildirir.

ControlsProviderService API'si, Reactive Streams GitHub projesinde tanımlandığı ve Java 9 Flow arayüzlerinde uygulandığı şekliyle reaktif akışlar hakkında bilgi sahibi olunduğunu varsayar. API aşağıdaki kavramları temel alır:

  • Yayıncı: Uygulamanız yayıncıdır.
  • Abone: Sistem kullanıcı arayüzü, abonedir ve yayıncıdan çeşitli kontroller isteyebilir.
  • Abonelik: Yayıncının sistem kullanıcı arayüzüne güncelleme gönderebileceği zaman aralığı. Yayıncı veya abone bu pencereyi kapatabilir.

Hizmeti beyan etme

Uygulamanızın manifest dosyasında bir hizmet (ör. MyCustomControlService) beyan etmesi gerekir.

Hizmet, ControlsProviderService için bir intent filtresi içermelidir. Bu filtre, uygulamaların sistem kullanıcı arayüzüne denetimler eklemesine olanak tanır.

Ayrıca, sistem kullanıcı arayüzündeki kontrollerde gösterilen bir label gerekir.

Aşağıdaki örnekte bir hizmetin nasıl beyan edileceği gösterilmektedir:

<service
    android:name="MyCustomControlService"
    android:label="My Custom Controls"
    android:permission="android.permission.BIND_CONTROLS"
    android:exported="true"
    >
    <intent-filter>
      <action android:name="android.service.controls.ControlsProviderService" />
    </intent-filter>
</service>

Ardından, MyCustomControlService.kt adlı yeni bir Kotlin dosyası oluşturun ve ControlsProviderService'i genişletmesini sağlayın:

class MyCustomControlService : ControlsProviderService() {
    // ...
}

Doğru kontrol türünü seçme

API, kontrolleri oluşturmak için oluşturucu yöntemleri sağlar. Oluşturucuyu doldurmak için kontrol etmek istediğiniz cihazı ve kullanıcının bu cihazla nasıl etkileşimde bulunduğunu belirleyin. Aşağıdaki adımları uygulayın:

  1. Kontrolün temsil ettiği cihaz türünü seçin. DeviceTypes sınıfı, desteklenen tüm cihazların bir listesidir. Tür, kullanıcı arayüzünde cihazın simgelerini ve renklerini belirlemek için kullanılır.
  2. Kullanıcıya yönelik adı, cihazın konumunu (ör. mutfak) ve kontrolle ilişkili diğer kullanıcı arayüzü metin öğelerini belirleyin.
  3. Kullanıcı etkileşimini desteklemek için en iyi şablonu seçin. Kontrollere uygulamadan bir ControlTemplate atanır. Bu şablon, kontrol durumunu ve kullanılabilir giriş yöntemlerini (yani ControlAction) doğrudan kullanıcıya gösterir. Aşağıdaki tabloda, mevcut şablonlardan bazıları ve destekledikleri işlemler açıklanmaktadır:
Şablon İşlem Açıklama
ControlTemplate.getNoTemplateObject() None Uygulama, denetimle ilgili bilgi vermek için bunu kullanabilir ancak kullanıcı bununla etkileşimde bulunamaz.
ToggleTemplate BooleanAction Etkin ve devre dışı durumları arasında geçiş yapılabilen bir denetimi temsil eder. BooleanAction nesnesi, kullanıcı kontrole dokunduğunda istenen yeni durumu temsil edecek şekilde değişen bir alan içerir.
RangeTemplate FloatAction Belirtilen minimum, maksimum ve adım değerlerine sahip bir kaydırma çubuğu widget'ını temsil eder. Kullanıcı kaydırma çubuğuyla etkileşimde bulunduğunda, güncellenmiş değerle birlikte uygulamaya yeni bir FloatAction nesnesi gönderin.
ToggleRangeTemplate BooleanAction, FloatAction Bu şablon, ToggleTemplate ve RangeTemplate şablonlarının bir kombinasyonudur. Dokunma etkinliklerinin yanı sıra, ışıkların parlaklığını kontrol etmek için kullanılan kaydırma çubuğu gibi özellikleri de destekler.
TemperatureControlTemplate ModeAction, BooleanAction, FloatAction Bu şablon, önceki işlemleri kapsamanın yanı sıra kullanıcının ısıtma, soğutma, ısıtma/soğutma, eko veya kapalı gibi bir mod ayarlamasına olanak tanır.
StatelessTemplate CommandAction Dokunma özelliği sağlayan ancak durumu belirlenemeyen bir kontrolü (ör. kızılötesi televizyon kumandası) belirtmek için kullanılır. Bu şablonu kullanarak rutin veya makro tanımlayabilirsiniz. Rutin ve makro, kontrol ve durum değişikliklerinin bir araya getirilmesiyle oluşturulur.

Bu bilgilerle kontrolü oluşturabilirsiniz:

Örneğin, bir akıllı ışık ve termostatı kontrol etmek için MyCustomControlService dosyanıza aşağıdaki sabitleri ekleyin:

private const val LIGHT_ID = 1234
private const val LIGHT_TITLE = "My fancy light"
private const val LIGHT_TYPE = DeviceTypes.TYPE_LIGHT
private const val THERMOSTAT_ID = 5678
private const val THERMOSTAT_TITLE = "My fancy thermostat"
private const val THERMOSTAT_TYPE = DeviceTypes.TYPE_THERMOSTAT

class MyCustomControlService : ControlsProviderService() {
    // ...
}

Kontroller için yayıncı oluşturma

Oluşturduğunuz denetimin bir yayıncısı olması gerekir. Yayıncı, sistem kullanıcı arayüzünü kontrolün varlığı konusunda bilgilendirir. ControlsProviderService sınıfında, uygulama kodunuzda geçersiz kılmanız gereken iki yayıncı yöntemi vardır:

  • createPublisherForAllAvailable: Uygulamanızda kullanılabilen tüm kontroller için bir Publisher oluşturur. Bu yayıncı için Control nesneleri oluşturmak üzere Control.StatelessBuilder kullanın.
  • createPublisherFor: Dize tanımlayıcılarıyla tanımlanan belirli denetimlerin listesi için bir Publisher oluşturur. Yayıncının her bir denetime bir durum ataması gerektiğinden bu Control nesnelerini oluşturmak için Control.StatefulBuilder kullanın.

Yayıncıyı oluşturma

Uygulamanız, kontrolleri sistem kullanıcı arayüzünde ilk kez yayınladığında her kontrolün durumunu bilmez. Durumu almak, cihaz sağlayıcının ağında birçok atlama içeren zaman alıcı bir işlem olabilir. Mevcut kontrolleri sisteme bildirmek için createPublisherForAllAvailable yöntemini kullanın. Her bir kontrolün durumu bilinmediğinden bu yöntem, Control.StatelessBuilder oluşturucu sınıfını kullanır.

Kontroller Android kullanıcı arayüzünde göründükten sonra kullanıcılar favori kontrolleri seçebilir.

ControlsProviderService oluşturmak için Kotlin eş yordamlarını kullanmak istiyorsanız build.gradle dosyanıza yeni bir bağımlılık ekleyin:

Modern

dependencies {
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk9:1.6.4"
}

Kotlin

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk9:1.6.4")
}

Gradle dosyalarınızı senkronize ettikten sonra createPublisherForAllAvailable uygulamasını uygulamak için Service dosyanıza aşağıdaki snippet'i ekleyin:

class MyCustomControlService : ControlsProviderService() {

    override fun createPublisherForAllAvailable(): Flow.Publisher<Control> =
        flowPublish {
            send(createStatelessControl(LIGHT_ID, LIGHT_TITLE, LIGHT_TYPE))
            send(createStatelessControl(THERMOSTAT_ID, THERMOSTAT_TITLE, THERMOSTAT_TYPE))
        }

    private fun createStatelessControl(id: Int, title: String, type: Int): Control {
        val intent = Intent(this, MainActivity::class.java)
            .putExtra(EXTRA_MESSAGE, title)
            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        val action = PendingIntent.getActivity(
            this,
            id,
            intent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
        )

        return Control.StatelessBuilder(id.toString(), action)
            .setTitle(title)
            .setDeviceType(type)
            .build()
    }

    override fun createPublisherFor(controlIds: List<String>): Flow.Publisher<Control> {
        TODO()
    }

    override fun performControlAction(
        controlId: String,
        action: ControlAction,
        consumer: Consumer<Int>,
    ) {
        TODO()
    }
}

Sistem menüsünü aşağı kaydırın ve Şekil 4'te gösterilen Cihaz denetimleri düğmesini bulun:

Cihaz kontrolleri için sistem kullanıcı arayüzü
Şekil 4. Sistem menüsündeki cihaz kontrolleri.

Cihaz kontrolleri'ne dokunduğunuzda uygulamanızı seçebileceğiniz ikinci bir ekrana yönlendirilirsiniz. Uygulamanızı seçtikten sonra, önceki snippet'in yeni kontrollerinizi gösteren özel bir sistem menüsünü nasıl oluşturduğunu Şekil 5'te gösterildiği gibi görebilirsiniz:

Işık ve termostat kontrolünü gösteren sistem menüsü
5.şekil Işık ve termostat kontrolleri eklenecek.

Şimdi createPublisherFor yöntemini uygulayın ve Service dosyanıza aşağıdakileri ekleyin:

private val job = SupervisorJob()
private val scope = CoroutineScope(Dispatchers.IO + job)
private val controlFlows = mutableMapOf<String, MutableSharedFlow<Control>>()

private var toggleState = false
private var rangeState = 18f

override fun createPublisherFor(controlIds: List<String>): Flow.Publisher<Control> {
    val flow = MutableSharedFlow<Control>(replay = 2, extraBufferCapacity = 2)

    controlIds.forEach { controlFlows[it] = flow }

    scope.launch {
        delay(1000) // Retrieving the toggle state.
        flow.tryEmit(createLight())

        delay(1000) // Retrieving the range state.
        flow.tryEmit(createThermostat())
    }
    return flow.asPublisher()
}

private fun createLight() = createStatefulControl(
    LIGHT_ID,
    LIGHT_TITLE,
    LIGHT_TYPE,
    toggleState,
    ToggleTemplate(
        LIGHT_ID.toString(),
        ControlButton(
            toggleState,
            toggleState.toString().uppercase(Locale.getDefault()),
        ),
    ),
)

private fun createThermostat() = createStatefulControl(
    THERMOSTAT_ID,
    THERMOSTAT_TITLE,
    THERMOSTAT_TYPE,
    rangeState,
    RangeTemplate(
        THERMOSTAT_ID.toString(),
        15f,
        25f,
        rangeState,
        0.1f,
        "%1.1f",
    ),
)

private fun <T> createStatefulControl(
    id: Int,
    title: String,
    type: Int,
    state: T,
    template: ControlTemplate,
): Control {
    val intent = Intent(this, MainActivity::class.java)
        .putExtra(EXTRA_MESSAGE, "$title $state")
        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    val action = PendingIntent.getActivity(
        this,
        id,
        intent,
        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
    )

    return Control.StatefulBuilder(id.toString(), action)
        .setTitle(title)
        .setDeviceType(type)
        .setStatus(Control.STATUS_OK)
        .setControlTemplate(template)
        .build()
}

override fun onDestroy() {
    super.onDestroy()
    job.cancel()
}

Bu örnekte createPublisherFor yöntemi, uygulamanızın yapması gerekenlerin sahte bir uygulamasını içerir: Durumunu almak için cihazınızla iletişim kurmak ve bu durumu sisteme göndermek.

createPublisherFor yöntemi, gerekli Reactive Streams API'yi karşılamak için Kotlin eş yordamlarını ve akışlarını kullanarak aşağıdakileri yapar:

  1. Flow oluşturur.
  2. Bir saniye bekler.
  3. Akıllı ışığın durumunu oluşturur ve yayınlar.
  4. Bir saniye daha bekler.
  5. Termostatın durumunu oluşturur ve yayınlar.

İşlemleri yönetme

performControlAction yöntemi, kullanıcı yayınlanan bir kontrolle etkileşime geçtiğinde sinyal gönderir. Gönderilen ControlAction türü, işlemi belirler. Belirli bir kontrol için uygun işlemi gerçekleştirin ve ardından Android kullanıcı arayüzünde cihazın durumunu güncelleyin.

Örneği tamamlamak için Service dosyanıza aşağıdakileri ekleyin:

override fun performControlAction(
    controlId: String,
    action: ControlAction,
    consumer: Consumer<Int>,
) {
    controlFlows[controlId]?.let { flow ->
        when (controlId) {
            LIGHT_ID.toString() -> {
                consumer.accept(ControlAction.RESPONSE_OK)
                if (action is BooleanAction) toggleState = action.newState
                flow.tryEmit(createLight())
            }
            THERMOSTAT_ID.toString() -> {
                consumer.accept(ControlAction.RESPONSE_OK)
                if (action is FloatAction) rangeState = action.newValue
                flow.tryEmit(createThermostat())
            }
            else -> consumer.accept(ControlAction.RESPONSE_FAIL)
        }
    } ?: consumer.accept(ControlAction.RESPONSE_FAIL)
}

Uygulamayı çalıştırın, Cihaz kontrolleri menüsüne erişin ve ışık ile termostat kontrollerinizi görün.

Işık ve termostat gösteren kontroller
6.şekil Işık ve termostat kontrolleri.