Android 11 ve sonraki sürümlerde Hızlı Erişim Cihaz Kontrolleri özelliği, kullanıcının varsayılan başlatıcıdan üç etkileşimde ışık, termostat ve kamera gibi harici cihazları hızlı bir şekilde görüntülemesine ve kontrol etmesine olanak tanır. Cihaz OEM'si hangi başlatıcıyı kullanacağını seçer. Cihaz toplayıcılar (ör. Google Home) ve üçüncü taraf tedarikçi firma uygulamaları bu alanda gösterilecek cihazlar sağlayabilir. Bu sayfada, cihaz kontrollerini bu alanda nasıl göstereceğiniz ve bunları kontrol uygulamanıza nasıl bağlayacağınız gösterilmektedir.
Bu desteği eklemek için bir ControlsProviderService
oluşturup tanımlayın. 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 şekilde gösterildiği gibi beş cihaz kontrol widget'ı kullanılabilir:
|
|
|
|
|
Bir widget'a dokunup basılı tuttuğunuzda daha ayrıntılı 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şiyorsa varsayılan simgeyi ve rengi kullanı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 Android kullanıcı arayüzünün Cihaz kontrolleri alanında gösterilmesi gereken cihaz kontrolleri içerdiğini bildirir.
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 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ığı. Bu pencereyi yayıncı veya abone kapatabilir.
Hizmeti beyan etme
Uygulamanız, uygulama manifestinde MyCustomControlService
gibi bir hizmet tanımlamalıdır.
Hizmet, ControlsProviderService
için bir intent filtresi içermelidir. Bu filtre, uygulamaların sistem kullanıcı arayüzüne kontrol eklemesine olanak tanır.
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 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()
'u 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 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:
- 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. - Kullanıcıya yönelik adı, cihaz konumunu (ör. mutfak) ve kontrolle ilişkili diğer kullanıcı arayüzü metin öğelerini belirleyin.
- Kullanıcı etkileşimini desteklemek için en iyi şablonu seçin. Kontrollere uygulamadan bir
ControlTemplate
atanır. Bu şablon, kullanıcıya doğrudan kontrol durumunu ve mevcut giriş yöntemlerini (yaniControlAction
) gösterir. Aşağıdaki tabloda, mevcut şablonlardan bazıları ve destekledikleri işlemler özetlenmiştir:
Şablon | İşlem | Açıklama |
ControlTemplate.getNoTemplateObject()
|
None
|
Uygulama, kontrol hakkında bilgi vermek için bunu kullanabilir ancak kullanıcı bu öğeyle etkileşim kuramaz. |
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 ç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 'nin bir kombinasyonudur. Hem dokunma etkinliklerini hem de kaydırma çubuğunu destekler. Örneğin, kısılabilir ışıkları kontrol etmek için kaydırma çubuğunu kullanabilirsiniz.
|
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 sunan ancak durumu belirlenemeyen bir kontrolü belirtmek için kullanılır (ör. kızılötesi televizyon uzaktan kumandası). Kontrol ve durum değişikliklerinin bir toplamı olan rutin veya makro tanımlamak için bu şablonu kullanabilirsiniz. |
Bu bilgilerle kontrolü oluşturabilirsiniz:
- Denetimin durumu bilinmediğinde
Control.StatelessBuilder
oluşturucu sınıfını kullanın. - Kontrolün durumu bilindiğinde
Control.StatefulBuilder
oluşturucu sınıfını kullanın.
Örneğin, akıllı ampul ve termostatı kontrol etmek için MyCustomControlService
öğenize aşağıdaki sabitleri 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ılar oluşturun
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ında, uygulama kodunuzda geçersiz kılmanız gereken iki yayıncı yöntemi vardır:
createPublisherForAllAvailable()
: Uygulamanızdaki tüm kontroller için birPublisher
oluşturur. Bu yayıncı içinControl
nesneleri oluşturmak üzereControl.StatelessBuilder()
'i kullanın.createPublisherFor()
: Belirli denetimlerin listesi için dize tanımlayıcılarıyla tanımlanan birPublisher
oluşturur. Yayıncının her denetime bir durum ataması gerektiğinden, buControl
nesnelerini oluşturmak içinControl.StatefulBuilder
kullanın.
Yayıncıyı oluşturma
Uygulamanız, kontrolleri sistem kullanıcı arayüzüne 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 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ğı kaydırın ve Şekil 4'te gösterilen Cihaz kontrolleri 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ü oluşturduğunu görürsünüz (Şekil 5).
Ardından, createPublisherFor()
yöntemini uygulayarak Service
dosyanıza aşağıdakileri ekleyin:
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.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 işlemin sahte bir uygulamasını içerir: Cihazınızın durumunu almak için cihazınızla iletişim kurar ve bu durumu sisteme gönderir.
createPublisherFor()
yöntemi, aşağıdakileri yaparak gerekli Reactive Streams API'yi karşılamak için Kotlin coroutine'lerini ve akışlarını kullanır:
- Bir
Flow
oluşturur. - Bir saniye bekleyin.
- Akıllı ışığın durumunu oluşturur ve yayınlar.
- Bir saniye daha bekler.
- 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.
Belirtilen 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:
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 ve ışık ve termostat kontrollerinizi görün.