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. Kullanacağı başlatıcıyı cihazın OEM'si 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, farklı kullanıcılara 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. Şunu oluşturun: bağlı olarak uygulamanızın desteklediğini kontrol edebilir ve ardından bu kontroller için yayıncıdan yararlanabilir.

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çılamaz veya kapatılamaz)
Durum bilgisiz açma/kapatma widget'ı
Durum bilgisiz açma/kapatma
Sıcaklık paneli widget'ı (kapalı)
Sıcaklık paneli (kapalı)
Şekil 2. Şablonlu widget koleksiyonu.

Dokunma ve widget'ı basılı tuttuğunuzda daha ayrıntılı kontrol için uygulamaya gidebilirsiniz. Şunları yapabilirsiniz: 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 kullanmalıdır.

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

Hizmeti oluşturma

Bu bölümde, ControlsProviderService. 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, aşağıdaki gibi reaktif akışlar hakkında bilgi sahibi olduğunu varsayar: Reaktif Akışlar GitHub'ında tanımlanmıştır proje ve Java 9 Akışı'nda uygulandı arayüzler. API aşağıdaki kavramlar temel alınarak oluşturulmuştur:

  • Yayıncı: Uygulamanız yayıncıdır.
  • Abone: Sistem kullanıcı arayüzü, abonedir ve sayı isteyebilir. kontrol etmek için kullanır.
  • Abonelik: Yayıncının güncellemeleri gönderebileceği zaman aralığı gönderebilirsiniz. Yayıncı veya abone bu ekranı kapatabilir penceresini kapatın.

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 denetimlerde görüntülenen bir label öğesine 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>

Sonra, MyCustomControlService.kt adında yeni bir Kotlin dosyası oluşturun ve ControlsProviderService() süresini uzat:

Kotlin

    class MyCustomControlService : ControlsProviderService() {
        ...
    }
    

Java

    public class MyCustomJavaControlService extends ControlsProviderService {
        ...
    }
    

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

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ının gördüğü adı, cihazın konumunu belirleyin. Örneğin, mutfak ve kontrolle ilişkili diğer kullanıcı arayüzü metin öğeleri.
  3. Kullanıcı etkileşimini desteklemek için 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, kullanılabilir şablonlar ve işlemler özetlenmektedir: şunları desteklerler:
Ş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ş yapabilen bir kontrolü temsil eder eyaletler. BooleanAction nesnesi, değişen bir alan içeriyor olarak, kullanıcı denetime dokunduğunda istenen yeni durumu temsil eder.
RangeTemplate FloatAction Belirtilen min, maks. ve adım değerlerine sahip bir kaydırma çubuğu widget'ını gösterir. Zaman Kullanıcı kaydırma çubuğuyla etkileşimde bulunursa yeni bir FloatAction gönderin nesnesini güncellenmiş değerle uygulamaya geri döndürmenizi sağlar.
ToggleRangeTemplate BooleanAction, FloatAction Bu şablon, ToggleTemplate ve RangeTemplate. Kaydırma çubuğunun yanı sıra dokunma etkinliklerini de destekler. Örneğin kısılabilir ışıkları kontrol edebilirler.
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. Bunu kullanabilirsiniz kontrolün toplamı olan rutin veya makroyu tanımlamak için kullanılan şablon ve durum değişiklikleri.

Bu bilgileri kullanarak denetimi 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ı, kontrol varlığının sistem kullanıcı arayüzü. ControlsProviderService sınıfı uygulama kodunuzda geçersiz kılmanız gereken iki yayıncı yöntemi vardır:

  • createPublisherForAllAvailable(): oluşturur Publisher kullandığınızdan emin olun. Control.StatelessBuilder() kullan kullanarak Control nesne oluşturabilirsiniz.
  • createPublisherFor(): Belirtilen denetimlerin listesi için bir Publisher oluşturur, dize tanımlayıcılarına göre tanımlanır. Ş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. Şunu kullanın: createPublisherForAllAvailable() yöntemini kullanabilirsiniz. Bu yöntem Her bir kontrolün durumu şu olduğundan Control.StatelessBuilder derleyici sınıfı bilinmiyor.

Kontroller Android arayüzünde göründükten sonra kullanıcı favorilerini seçebilir kontrol eder.

Kotlin eş yordamlarını kullanarak ControlsProviderService oluşturmak için, yeni bir bağımlılığınız: build.gradle:

Eski

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 aşağıdaki snippet'i Service createPublisherForAllAvailable() uygulayın:

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> 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, seçim yapabileceğiniz ikinci bir ekrana gidersiniz en iyi şekilde yararlanabilirsiniz. Uygulamanızı seçtiğinizde önceki snippet'in Şekil 5'te gösterildiği gibi yeni denetimlerinizi gösteren özel sistem menüsü:

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

Ş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>()
 
    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. 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ı bir yayınlanmış kontrolde bulabilirsiniz. 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, ışığınızı ve termostat kontrollerine dokunun.

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