Android 11 ve sonraki sürümlerde Hızlı Erişim Cihaz Kontrolleri özelliği, kullanıcının ışık, termostat ve kamera gibi harici cihazları, varsayılan bir başlatıcıdan üç etkileşimde bulunarak hızla görüntülemesini ve kontrol etmesini sağlar. Cihazın OEM hangi başlatıcıyı kullanacağını seçer. Cihaz toplayıcılar (ör. Google Home) ve üçüncü taraf tedarikçi uygulamaları, bu alanda görüntülenecek cihazlar sağlayabilir. Bu sayfada, cihaz denetimlerinin bu alanda nasıl görüneceği ve kontrol uygulamanıza nasıl bağlanacağı gösterilmektedir.
Bu desteği eklemek için bir ControlsProviderService
oluşturup beyan edin. Önceden tanımlanmış kontrol türlerine göre uygulamanızın desteklediği denetimleri ve ardından bu denetimler için yayıncılar oluşturun.
Kullanıcı arayüzü
Cihazlar, şablonlu widget'lar olarak Cihaz denetimleri altında gösterilir. Aşağıdaki şekilde gösterildiği gibi beş adet cihaz kontrol widget'ı mevcuttur:
|
|
|
|
|
Bir widget'a dokunup basılı tuttuğunuzda daha fazla kontrol için uygulamaya yönlendirilirsiniz. Her widget'ın simgesini ve rengini özelleştirebilirsiniz, ancak en iyi kullanıcı deneyimi için varsayılan ayar cihazla eşleşirse varsayılan simge ve rengi kullanın.
Hizmeti oluşturma
Bu bölümde, ControlsProviderService
'in 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, Reaktif Akışlar GitHub projesinde tanımlandığı ve Java 9 Akış arayüzlerinde uygulanan reaktif akışlar hakkında bilgi sahibi olduğunu varsayar.
API, aşağıdaki kavramlar çerçevesinde derlenmiştir:
- 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, uygulama manifest dosyasında MyCustomControlService
gibi bir hizmeti beyan etmelidir.
Hizmet, ControlsProviderService
için bir intent filtresi içermelidir. Bu filtre, uygulamaların sistem kullanıcı arayüzüne kontrol katkısında bulunmasını sağlar.
Ayrıca, sistem kullanıcı arayüzündeki kontrollerde görüntülenen bir label
öğesine ihtiyacınız vardır.
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 bu dosyayı ControlsProviderService()
uzantısına getirin:
Kotlin
class MyCustomControlService : ControlsProviderService() { ... }
Java
public class MyCustomJavaControlService extends ControlsProviderService { ... }
Doğru kontrol türünü seçme
API, denetimleri 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 bulunacağını belirleyin. Aşağıdaki adımları uygulayın:
- Kontrolün temsil ettiği cihaz türünü seçin.
DeviceTypes
sınıfı, desteklenen tüm cihazların sıralamasıdır. Bu tür, kullanıcı arayüzünde cihazın simgelerinin ve renklerinin belirlenmesi için kullanılır. - Kullanıcıya görünen adı, cihazın konumunu (ör. mutfak) ve kontrolle ilişkili diğer kullanıcı arayüzü metin öğelerini belirleyin.
- Kullanıcı etkileşimini destekleyecek en iyi şablonu seçin. Kontrollere uygulamadan
ControlTemplate
atanır. Bu şablon, kontrol durumunu ve kullanılabilir giriş yöntemlerini (yaniControlAction
) doğrudan kullanıcıya gösterir. Aşağıdaki tabloda, kullanılabilir şablonlardan bazıları ve destekledikleri işlemler özetlenmiştir:
Şablon | İşlem | Description |
ControlTemplate.getNoTemplateObject()
|
None
|
Uygulama, kontrol hakkında bilgi aktarmak için bunu kullanabilir, ancak kullanıcı etkileşimde bulunamaz. |
ToggleTemplate
|
BooleanAction
|
Etkin ve devre dışı durumları arasında geçiş yapılabilen bir kontrolü 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 uygulamaya güncellenen değerle yeni bir FloatAction nesnesi gönderin.
|
ToggleRangeTemplate
|
BooleanAction, FloatAction
|
Bu şablon, ToggleTemplate ve RangeTemplate öğelerinin kombinasyonudur. Dokunma etkinliklerini ve kısılabilir ışıkları kontrol etme gibi bir kaydırma çubuğunu 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 da olanak tanır. |
StatelessTemplate
|
CommandAction
|
Kızılötesi televizyon uzaktan kumandası gibi, dokunma özelliği sağlayan ancak durumu belirlenemeyen bir kontrolü belirtmek için kullanılır. Kontrol ve durum değişikliklerinin toplamı olan bir rutin veya makro tanımlamak için bu şablonu kullanabilirsiniz. |
Bu bilgileri kullanarak aşağıdakileri kontrol edebilirsiniz:
- Kontrolün durumu bilinmiyorsa
Control.StatelessBuilder
geliştirici sınıfını kullanın. - Kontrolün durumu bilindiğinde
Control.StatefulBuilder
geliştirici sınıfını kullanın.
Örneğin, akıllı ampulü ve termostatı kontrol etmek için MyCustomControlService
cihazınıza aşağıdaki sabit değerleri ekleyin:
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ı oluşturma
Kontrolü oluşturmanızın ardından kontrolün bir yayıncısı olması gerekir. Yayıncı, sistem kullanıcı arayüzüne denetimin varlığı hakkında bilgi verir. ControlsProviderService
sınıfı, uygulama kodunuzda geçersiz kılmanız gereken iki yayıncı yöntemi içerir:
createPublisherForAllAvailable()
: uygulamanızda kullanılabilen tüm denetimler için birPublisher
oluşturur. Bu yayıncı içinControl
nesneleri oluşturmak içinControl.StatelessBuilder()
kullanın.createPublisherFor()
: Dize tanımlayıcılarıyla tanımlanan kontrollerin listesi için birPublisher
oluşturur. Yayıncının her kontrole bir durum ataması gerektiğinden buControl
nesnelerini oluşturmak içinControl.StatefulBuilder
öğesini kullanın.
Yayıncıyı oluşturma
Uygulamanız sistem kullanıcı arayüzünde kontrolleri ilk kez yayınladığında her kontrolün durumunu bilmez. Durum bilgisini almak, cihaz sağlayıcısının ağında çok sayıda atlama
içeren zaman alan bir işlem olabilir. Mevcut denetimleri sisteme tanıtmak 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ı favori kontrolleri seçebilir.
Kotlin eş yordamlarını kullanarak ControlsProviderService
oluşturmak için build.gradle
cihazını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
öğenize 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> 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ğı kaydırın ve şekil 4'te gösterilen Cihaz denetimleri düğmesini bulun:
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österilmiştir:
Şimdi aşağıdaki kodu Service
öğenize ekleyerek createPublisherFor()
yöntemini uygulayın:
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.PublishercreatePublisherFor(@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, uygulamanızın yapması gereken şeyi şu şekilde sahte bir uygulama içerir: durumunu almak için cihazınızla iletişim kurma ve bu durumu sisteme bildirme.
createPublisherFor()
yöntemi, Kotlin eş yordamlarını ve akışlarını kullanarak gerekli Reaktif Streams API'yi karşılamak için aşağıdakileri yapar:
Flow
oluşturur.- Bir saniye bekler.
- Akıllı ışığın durumunu oluşturur ve yayar.
- Bir saniye daha bekler.
- Termostatın durumunu oluşturur ve yayınlar.
İşlem yapma
performControlAction()
yöntemi, kullanıcı yayınlanan bir kontrolle etkileşimde bulunduğunda sinyal gönderir. Gönderilen ControlAction
türü işlemi belirler.
Verilen 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 aşağıdaki kodu Service
öğenize 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 Consumerconsumer) { 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, ışık ve termostat kontrollerinizi görün.