Harici cihazları kontrol etme

Android 11 ve sonraki sürümlerde Hızlı Erişim Cihaz Kontrolleri özelliği kullanıcının lambalar, telefon numaraları, e-posta gibi harici cihazlar gibi harici cihazları cihazları ve kameraları, tek bir konumdan üç etkileşim içinde varsayılan başlatıcıdır. Cihaz OEM'si hangi başlatıcıyı kullanacağını seçer. Cihaz Google Home ve üçüncü taraf tedarikçi firma uygulamaları gibi bu alanda görüntülenecek cihazlar sağlayın. Bu sayfada, Google Haberler'de kontrol uygulamanıza bağlayabilirsiniz.

Ş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 altında şablonlu widget'lar olarak gösterilir. Beş cihaz denetimi widget'ları aşağıdaki şekilde gösterildiği gibi kullanılabilir:

Widget'ı aç/kapat
Aç/Kapat
Kaydırma çubuğu widget'ıyla aç/kapat
Kaydırma çubuğuyla aç/kapat
Aralık widget'ı
Aralık (açma/kapatma düğmesi yoktur)
Durum bilgisiz açma/kapatma widget'ı
Durum bilgisiz açma/kapatma düğmesi
Sıcaklık paneli widget'ı (kapalı)
Sıcaklık paneli (kapalı)
Şekil 2. Şablonlu widget koleksiyonu.

Bir widget'a dokunup basılı tutarak daha ayrıntılı kontrol için uygulamaya gidebilirsiniz. Her widget'ın simgesini ve rengini özelleştirebilirsiniz ancak en iyi kullanıcı deneyimi için varsayılan ayar cihazla eşleşiyorsa varsayılan simgeyi ve rengi kullanın.

Sıcaklık paneli widget'ını gösteren resim (açık)
Şekil 3. Sıcaklık paneli widget'ını açın.

Hizmeti oluşturun

Bu bölümde, ControlsProviderService'nin nasıl oluşturulacağı gösterilmektedir. Bu hizmet, Android sistem kullanıcı arayüzüne uygulamanızın cihaz kontrolleri içerdiğini bildirir bölümünde gösterilmesi gerekir.

ControlsProviderService API, Reactive Streams GitHub projesinde tanımlanan ve Java 9 Flow arayüzlerinde uygulanan reaktif akışlara aşina olduğunuzu varsayar. API aşağıdaki kavramlar temel alınarak oluşturulmuştur:

  • Yayıncı: Yayıncı, uygulamanızdır.
  • Abone: Sistem kullanıcı arayüzü, abonedir ve sayı isteyebilir. kontrol etmek için kullanır.
  • Abonelik: Yayıncının sistem kullanıcı arayüzüne güncelleme gönderebileceği zaman aralığı. Bu pencereyi yayıncı veya abone kapatabilir.

Hizmeti beyan etme

Uygulamanız MyCustomControlService gibi bir hizmeti bildirmelidir.

Hizmet, ControlsProviderService için bir intent filtresi içermelidir. Bu filtre, uygulamaların sistem arayüzüne kontrol katkısında bulunmalarını sağlar.

Ayrıca, sistem kullanıcı arayüzündeki kontrol panellerinde gösterilen bir label'e de ihtiyacınız vardır.

Aşağıdaki örnekte bir hizmetin nasıl tanımlanacağı 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()'yi genişletin:

Kotlin

    class MyCustomControlService : ControlsProviderService() {
        ...
    }
    

Java

    public class MyCustomJavaControlService extends ControlsProviderService {
        ...
    }
    

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

API, kontrolleri oluşturmak için oluşturucu yöntemleri sunar. oluşturucu, kontrol etmek istediğiniz cihazı ve kullanıcının nasıl etkileşimde bulunacağını belirleyin gerçekleşebilir. Aşağıdaki adımları uygulayın:

  1. Kontrolün temsil ettiği cihaz türünü seçin. İlgili içeriği oluşturmak için kullanılan DeviceTypes sınıfı bir desteklenen tüm cihazların numaralandırması. Tür, renklerini ve simgelerini görebilirsiniz.
  2. Kullanıcıya yönelik adı, cihaz konumunu (ör. mutfak) ve kontrolle ilişkili diğer kullanıcı arayüzü metin öğelerini belirleyin.
  3. Kullanıcı etkileşimini destekleyen en iyi şablonu seçin. Kontrollere ControlTemplate kaldırıyor. Bu şablon, doğrudan hem de kullanılabilir giriş yöntemlerini (yani ControlAction. Aşağıdaki tabloda, mevcut şablonlardan bazıları ve destekledikleri işlemler özetlenmiştir:
Şablon İşlem Açıklama
ControlTemplate.getNoTemplateObject() None Uygulama bunu kontrol, kontrol, kullanıcı ve uygulama ancak kullanıcı onunla etkileşim kuramaz.
ToggleTemplate BooleanAction Etkin ve devre dışı durumları arasında geçiş yapılabilen bir kontrolü temsil eder. BooleanAction nesnesi, değişen bir alan içeriyor olarak, kullanıcı denetime dokunduğunda istenen yeni durumu temsil eder.
RangeTemplate FloatAction Belirtilen minimum, maksimum ve adım değerlerine sahip bir kaydırma çubuğu widget'ını temsil eder. Kullanıcı, kaydırma çubuklarıyla etkileşime girdiğinde uygulamaya güncellenmiş değeri içeren yeni bir FloatAction nesnesi gönderin.
ToggleRangeTemplate BooleanAction, FloatAction Bu şablon, ToggleTemplate ve RangeTemplate. Dokunma etkinliklerinin yanı sıra karartılabilir ışıkları kontrol etmek için kullanılan kaydırma çubuklarını da destekler.
TemperatureControlTemplate ModeAction, BooleanAction, FloatAction Bu şablon, önceki işlemleri kapsüllemenin yanı sıra Kullanıcı ısıtma, soğutma, ısıtma/soğutma, eko veya kapalı gibi bir mod ayarlar.
StatelessTemplate CommandAction Dokunma yeteneği sağlayan ancak durumu ancak kızılötesi bir televizyon gibi belirlenemiyor. Kontrol ve durum değişikliklerinin bir toplamı olan rutin veya makro tanımlamak için bu şablonu kullanabilirsiniz.

Bu bilgilerle kontrolü oluşturabilirsiniz:

Örneğin, bir akıllı ampulü ve bir termostatı kontrol etmek için aşağıdakini ekleyin MyCustomControlService sabit değerleri:

Kotlin

    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() {
      ...
    }
    

Java

    public class MyCustomJavaControlService extends ControlsProviderService {
 
    private final int LIGHT_ID = 1337;
    private final String LIGHT_TITLE = "My fancy light";
    private final int LIGHT_TYPE = DeviceTypes.TYPE_LIGHT;
    private final int THERMOSTAT_ID = 1338;
    private final String THERMOSTAT_TITLE = "My fancy thermostat";
    private final int THERMOSTAT_TYPE = DeviceTypes.TYPE_THERMOSTAT;
 
    ...
    }
    

Kontroller için yayıncılar oluşturma

Kontrolü oluşturduktan sonra bir yayıncıya ihtiyacı vardır. Yayıncı, sistem kullanıcı arayüzünü kontrolün varlığı hakkında bilgilendirir. ControlsProviderService sınıfı uygulama kodunuzda geçersiz kılmanız gereken iki yayıncı yöntemi vardır:

  • createPublisherForAllAvailable(): Uygulamanızdaki tüm kontroller için bir Publisher oluşturur. Bu yayıncı için Control nesneleri oluşturmak üzere Control.StatelessBuilder()'i kullanın.
  • createPublisherFor(): Belirli denetimlerin listesi için dize tanımlayıcılarıyla tanımlanan bir Publisher oluşturur. Şunlar için Control.StatefulBuilder kullanın: bu Control nesnesini oluşturur, çünkü yayıncının her bir kontrole dokunun.

Yayıncıyı oluşturma

Uygulamanız, kontrolleri sistem kullanıcı arayüzüne ilk kez yayınladığında uygulama bunu bilmez. durumu hakkında bilgi edinin. Durum bilgisine ulaşmak zaman alıcı bir işlem olabilir. çok sayıda atlama olması gerekir. Mevcut denetimlerin sisteme tanıtılması için createPublisherForAllAvailable() yöntemini kullanın. Her bir kontrolün durumu bilinmediğinden bu yöntemde Control.StatelessBuilder oluşturucu sınıfı kullanılır.

Kontroller Android kullanıcı arayüzünde göründüğünde kullanıcı 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:

Groovy

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() özelliğini uygulamak için Service dosyanıza aşağıdaki snippet'i ekleyin:

Kotlin

    class MyCustomControlService : ControlsProviderService() {
 
      override fun createPublisherForAllAvailable(): Flow.Publisher =
          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): Flow.Publisher {
           TODO()
        }
 
        override fun performControlAction(
            controlId: String,
            action: ControlAction,
            consumer: Consumer
        ) {
            TODO()
        }
    }
    

Java

    public class MyCustomJavaControlService extends ControlsProviderService {
 
        private final int LIGHT_ID = 1337;
        private final String LIGHT_TITLE = "My fancy light";
        private final int LIGHT_TYPE = DeviceTypes.TYPE_LIGHT;
        private final int THERMOSTAT_ID = 1338;
        private final String THERMOSTAT_TITLE = "My fancy thermostat";
        private final int THERMOSTAT_TYPE = DeviceTypes.TYPE_THERMOSTAT;
 
        private boolean toggleState = false;
        private float rangeState = 18f;
        private final Map<String, ReplayProcessor> controlFlows = new HashMap<>();
 
        @NonNull
        @Override
        public Flow.Publisher createPublisherForAllAvailable() {
            List controls = new ArrayList<>();
            controls.add(createStatelessControl(LIGHT_ID, LIGHT_TITLE, LIGHT_TYPE));
            controls.add(createStatelessControl(THERMOSTAT_ID, THERMOSTAT_TITLE, THERMOSTAT_TYPE));
            return FlowAdapters.toFlowPublisher(Flowable.fromIterable(controls));
        }
 
        @NonNull
        @Override
        public Flow.Publisher createPublisherFor(@NonNull List controlIds) {
            ReplayProcessor updatePublisher = ReplayProcessor.create();
 
            controlIds.forEach(control -> {
                controlFlows.put(control, updatePublisher);
                updatePublisher.onNext(createLight());
                updatePublisher.onNext(createThermostat());
            });
 
            return FlowAdapters.toFlowPublisher(updatePublisher);
        }
    }
    

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

Cihaz denetimleri için sistem kullanıcı arayüzünü gösteren resim
Şekil 4. Sistem menüsündeki cihaz denetimleri.

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ü oluşturduğunu görürsünüz (Şekil 5'te gösterilmiştir):

Işık ve termostat kontrolü içeren sistem menüsünü gösteren resim
Şekil 5. Eklenecek ışık ve termostat kontrolleri.

Şimdi, aşağıdaki kodu uygulamanıza ekleyerek createPublisherFor() yöntemini uygulayın: Service:

Kotlin

    private val job = SupervisorJob()
    private val scope = CoroutineScope(Dispatchers.IO + job)
    private val controlFlows = mutableMapOf<String, MutableSharedFlow>()
 
    private var toggleState = false
    private var rangeState = 18f
 
    override fun createPublisherFor(controlIds: List): Flow.Publisher {
        val flow = MutableSharedFlow(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  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()
    }
 
    

Java

    @NonNull
    @Override
    public Flow.Publisher createPublisherFor(@NonNull List controlIds) {
        ReplayProcessor updatePublisher = ReplayProcessor.create();
 
        controlIds.forEach(control -> {
            controlFlows.put(control, updatePublisher);
            updatePublisher.onNext(createLight());
            updatePublisher.onNext(createThermostat());
        });
 
        return FlowAdapters.toFlowPublisher(updatePublisher);
    }
 
    private Control createStatelessControl(int id, String title, int type) {
        Intent intent = new Intent(this, MainActivity.class)
                .putExtra(EXTRA_MESSAGE, title)
                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        PendingIntent action = PendingIntent.getActivity(
                this,
                id,
                intent,
                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
        );
 
        return new Control.StatelessBuilder(id + "", action)
                .setTitle(title)
                .setDeviceType(type)
                .build();
    }
 
    private Control createLight() {
        return createStatefulControl(
                LIGHT_ID,
                LIGHT_TITLE,
                LIGHT_TYPE,
                toggleState,
                new ToggleTemplate(
                        LIGHT_ID + "",
                        new ControlButton(
                                toggleState,
                                String.valueOf(toggleState).toUpperCase(Locale.getDefault())
                        )
                )
        );
    }
 
    private Control createThermostat() {
        return createStatefulControl(
                THERMOSTAT_ID,
                THERMOSTAT_TITLE,
                THERMOSTAT_TYPE,
                rangeState,
                new RangeTemplate(
                        THERMOSTAT_ID + "",
                        15f,
                        25f,
                        rangeState,
                        0.1f,
                        "%1.1f"
                )
        );
    }
 
    private  Control createStatefulControl(int id, String title, int type, T state, ControlTemplate template) {
        Intent intent = new Intent(this, MainActivity.class)
                .putExtra(EXTRA_MESSAGE, "$title $state")
                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        PendingIntent action = PendingIntent.getActivity(
                this,
                id,
                intent,
                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
        );
 
        return new Control.StatefulBuilder(id + "", action)
                .setTitle(title)
                .setDeviceType(type)
                .setStatus(Control.STATUS_OK)
                .setControlTemplate(template)
                .build();
    }
    

Bu örnekte, createPublisherFor() yöntemi sahte bir uygulamanızla iletişim kurma: alma ve bu durumu sisteme iletme.

createPublisherFor() yöntemi, aşağıdaki koşulu karşılamak için Kotlin eş yordamlarını ve akışlarını kullanır aşağıdaki işlemleri yaparak gerekli Reactive Streams API'sini isteyin:

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

İşlemleri işleme

performControlAction() yöntemi, kullanıcının yayınlanmış bir kontrolle etkileşime geçtiğinde sinyal gönderir. Gönderilen ControlAction türü, işlemi belirler. Verilen denetim için uygun işlemi gerçekleştirip durumu güncelleyin cihazın Android kullanıcı arayüzünde görebilirsiniz.

Örneği tamamlamak için Service öğenize aşağıdakileri ekleyin:

Kotlin

    override fun performControlAction(
        controlId: String,
        action: ControlAction,
        consumer: Consumer
    ) {
        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)
    }
    

Java

    @Override
    public void performControlAction(@NonNull String controlId, @NonNull ControlAction action, @NonNull Consumer consumer) {
        ReplayProcessor processor = controlFlows.get(controlId);
        if (processor == null) return;
 
        if (controlId.equals(LIGHT_ID + "")) {
            consumer.accept(ControlAction.RESPONSE_OK);
            if (action instanceof BooleanAction) toggleState = ((BooleanAction) action).getNewState();
            processor.onNext(createLight());
        }
        if (controlId.equals(THERMOSTAT_ID + "")) {
            consumer.accept(ControlAction.RESPONSE_OK);
            if (action instanceof FloatAction) rangeState = ((FloatAction) action).getNewValue()
            processor.onNext(createThermostat());
        }
    }
    

Uygulamayı çalıştırın, Cihaz kontrolleri menüsüne erişin ve ışığınızı ve termostat kontrollerine dokunun.

Işık ve termostat kontrolünü gösteren resim
Şekil 6. Işık ve termostat kontrolleri.