Di Android 11 dan yang lebih baru, fitur Kontrol Perangkat Akses Cepat memungkinkan pengguna melihat dan mengontrol perangkat eksternal dengan cepat, seperti lampu, termostat, dan kamera dari menu daya Android. Agregator perangkat (misalnya, Google Home) dan aplikasi vendor pihak ketiga dapat menyediakan perangkat untuk ditampilkan di ruang ini. Panduan ini menunjukkan cara memunculkan kontrol perangkat di ruang ini dan menautkannya ke aplikasi kontrol.
Untuk menambahkan dukungan ini, buat dan deklarasikan ControlsProviderService
, buat kontrol yang didukung aplikasi Anda berdasarkan jenis kontrol yang telah ditentukan, lalu buat penayang untuk kontrol ini.
Antarmuka pengguna
Perangkat ditampilkan di bagian Kontrol perangkat sebagai widget template. Tersedia lima widget kontrol perangkat:
![]() |
![]() |
![]() |
![]() |
![]() |

Menekan lama widget akan mengarahkan Anda ke aplikasi untuk kontrol yang lebih lengkap. Anda dapat menyesuaikan ikon dan warna setiap widget, tetapi untuk pengalaman pengguna terbaik, Anda harus menggunakan ikon dan warna default, kecuali kumpulan default yang tersedia tidak cocok dengan perangkat.
Membuat layanan
Bagian ini menunjukkan cara membuat ControlsProviderService
.
Layanan ini memberi tahu UI sistem Android bahwa aplikasi Anda menyertakan kontrol perangkat yang akan muncul di area Kontrol perangkat UI Android.
ControlsProviderService
API mengasumsikan pengetahuan tentang aliran reaktif, seperti yang dijelaskan dalam project Reactive Streams GitHub dan diimplementasikan dalam antarmuka Flow Java 9.
API ini dibuat berdasarkan konsep berikut:
- Penayang: Aplikasi Anda adalah penayang
- Pelanggan: UI sistem adalah pelanggan dan dapat meminta sejumlah kontrol dari penayang
- Langganan: Periode waktu saat penayang dapat mengirimkan update ke UI Sistem; periode ini dapat diakhiri oleh penayang atau pelanggan
Deklarasikan layanan
Aplikasi Anda harus mendeklarasikan layanan dalam manifes aplikasinya. Pastikan menyertakan izin BIND_CONTROLS
.
Layanan harus menyertakan filter intent untuk ControlsProviderService
. Filter ini memungkinkan aplikasi memberikan kontrol ke UI sistem.
<!-- New signature permission to ensure only systemui can bind to these services -->
<service android:name="YOUR-SERVICE-NAME" android:label="YOUR-SERVICE-LABEL"
android:permission="android.permission.BIND_CONTROLS">
<intent-filter>
<action android:name="android.service.controls.ControlsProviderService" />
</intent-filter>
</service>
Pilih jenis kontrol yang tepat
API menyediakan metode builder untuk membuat kontrol. Untuk mengisi builder, Anda perlu menentukan perangkat yang ingin dikontrol dan cara pengguna berinteraksi dengannya. Secara khusus, Anda perlu melakukan hal berikut:
- Pilih jenis perangkat yang diwakili oleh kontrol. Class
DeviceTypes
adalah enumerasi dari semua perangkat yang saat ini didukung. Jenis ini digunakan untuk menentukan ikon dan warna perangkat di UI. - Tentukan nama yang akan dilihat pengguna, lokasi perangkat (dapur, misalnya), dan elemen teks UI lainnya yang diatribusikan dengan kontrol.
- Pilih template terbaik untuk mendukung interaksi pengguna. Kontrol ditetapkan ke
ControlTemplate
dari aplikasi. Template ini secara langsung menunjukkan status kontrol kepada pengguna, beserta metode masukan yang tersedia (yaituControlAction
). Tabel berikut menguraikan beberapa template yang tersedia dan tindakan yang didukungnya:
Template | Tindakan | Deskripsi |
ControlTemplate.getNoTemplateObject()
|
None
|
Aplikasi dapat menggunakannya untuk menyampaikan informasi tentang kontrol, tetapi pengguna tidak dapat berinteraksi dengannya. |
ToggleTemplate
|
BooleanAction
|
Mewakili kontrol yang statusnya dapat dialihkan antara aktif dan nonaktif. Objek BooleanAction berisi kolom yang berubah untuk mewakili status baru yang diminta saat pengguna menyentuh kontrol.
|
RangeTemplate
|
FloatAction
|
Mewakili widget penggeser dengan nilai minimal, maksimal, dan langkah yang ditetapkan. Saat pengguna berinteraksi dengan penggeser, objek FloatAction baru akan dikirim kembali ke aplikasi dengan nilai yang sudah diperbarui.
|
ToggleRangeTemplate
|
BooleanAction, FloatAction
|
Template ini adalah kombinasi dari ToggleTemplate dan RangeTemplate . Template ini mendukung peristiwa sentuh serta penggeser, seperti pada kontrol untuk lampu yang dapat diredupkan, misalnya.
|
TemperatureControlTemplate
|
ModeAction, BooleanAction, FloatAction
|
Selain mengenkapsulasi salah satu tindakan di atas, template ini memungkinkan pengguna menentukan mode, seperti hangat, dingin, hangat/dingin, hemat energi, atau nonaktif. |
StatelessTemplate
|
CommandAction
|
Digunakan untuk menunjukkan kontrol yang menyediakan kemampuan sentuh tetapi statusnya tidak dapat ditentukan, misalnya remote televisi IR. Anda dapat menggunakan template ini untuk menentukan rutinitas atau makro, yang merupakan agregasi perubahan status dan kontrol. |
Dengan informasi ini, Anda kini dapat membuat kontrol:
- Gunakan class builder
Control.StatelessBuilder
jika status kontrol tidak diketahui. - Gunakan class builder
Control.StatefulBuilder
jika status kontrol diketahui.
Buat penayang untuk kontrol
Setelah dibuat, kontrol memerlukan penayang. Penayang memberi tahu UI sistem tentang keberadaan kontrol. Class ControlsProviderService
memiliki dua metode penayang yang harus Anda ganti dalam kode aplikasi:
createPublisherForAllAvailable()
: MembuatPublisher
untuk semua kontrol yang tersedia di aplikasi Anda. GunakanControl.StatelessBuilder()
untuk membuatControls
bagi penayang ini.createPublisherFor()
: MembuatPublisher
untuk daftar kontrol tertentu, seperti yang diidentifikasi oleh ID stringnya. GunakanControl.StatefulBuilder
untuk membuatControls
ini karena penayang harus menetapkan status bagi setiap kontrol.
Membuat penayang
Saat menayangkan kontrol ke UI sistem untuk kali pertama, aplikasi tidak mengetahui status setiap kontrol. Akan memerlukan banyak waktu untuk mendapatkan status ini, yang melibatkan banyak lonjakan pada jaringan penyedia perangkat. Gunakan metode createPublisherForAllAvailable()
untuk memberi tahu sistem tentang kontrol yang tersedia. Perlu diperhatikan bahwa metode ini menggunakan class builder Control.StatelessBuilder
karena status setiap kontrol tidak diketahui.
Setelah kontrol muncul di UI Android, pengguna dapat memilih kontrol (memilih favorit) yang diinginkan.
Kotlin
/* If you choose to use Reactive Streams API, you will need to put the following * into your module's build.gradle file: * implementation 'org.reactivestreams:reactive-streams:1.0.3' * implementation 'io.reactivex.rxjava2:rxjava:2.2.0' */ class MyCustomControlService : ControlsProviderService() { override fun createPublisherForAllAvailable(): Flow.Publisher{ val context: Context = baseContext val i = Intent() val pi = PendingIntent.getActivity( context, CONTROL_REQUEST_CODE, i, PendingIntent.FLAG_UPDATE_CURRENT ) val controls = mutableListOf () val control = Control.StatelessBuilder(MY-UNIQUE-DEVICE-ID, pi) // Required: The name of the control .setTitle(MY-CONTROL-TITLE) // Required: Usually the room where the control is located .setSubtitle(MY-CONTROL-SUBTITLE) // Optional: Structure where the control is located, an example would be a house .setStructure(MY-CONTROL-STRUCTURE) // Required: Type of device, i.e., thermostat, light, switch .setDeviceType(DeviceTypes.DEVICE-TYPE) // For example, DeviceTypes.TYPE_THERMOSTAT .build() controls.add(control) // Create more controls here if needed and add it to the ArrayList // Uses the RxJava 2 library return FlowAdapters.toFlowPublisher(Flowable.fromIterable(controls)) } }
Java
/* If you choose to use Reactive Streams API, you will need to put the following * into your module's build.gradle file: * implementation 'org.reactivestreams:reactive-streams:1.0.3' * implementation 'io.reactivex.rxjava2:rxjava:2.2.0' */ public class MyCustomControlService extends ControlsProviderService { @Override public PublishercreatePublisherForAllAvailable() { Context context = getBaseContext(); Intent i = new Intent(); PendingIntent pi = PendingIntent.getActivity(context, 1, i, PendingIntent.FLAG_UPDATE_CURRENT); List controls = new ArrayList<>(); Control control = new Control.StatelessBuilder(MY-UNIQUE-DEVICE-ID, pi) // Required: The name of the control .setTitle(MY-CONTROL-TITLE) // Required: Usually the room where the control is located .setSubtitle(MY-CONTROL-SUBTITLE) // Optional: Structure where the control is located, an example would be a house .setStructure(MY-CONTROL-STRUCTURE) // Required: Type of device, i.e., thermostat, light, switch .setDeviceType(DeviceTypes.DEVICE-TYPE) // For example, DeviceTypes.TYPE_THERMOSTAT .build(); controls.add(control); // Create more controls here if needed and add it to the ArrayList // Uses the RxJava 2 library return FlowAdapters.toFlowPublisher(Flowable.fromIterable(controls)); } }
Setelah pengguna memilih kumpulan kontrol, buat penayang hanya untuk kontrol tersebut. Gunakan metode createPublisherFor()
karena metode ini menggunakan class builder Control.StatefulBuilder
, yang menyediakan status saat ini dari setiap kontrol (misalnya aktif atau tidak aktif).
Kotlin
class MyCustomControlService : ControlsProviderService() { private lateinit var updatePublisher: ReplayProcessoroverride fun createPublisherFor(controlIds: MutableList ): Flow.Publisher { val context: Context = baseContext /* Fill in details for the activity related to this device. On long press, * this Intent will be launched in a bottomsheet. Please design the activity * accordingly to fit a more limited space (about 2/3 screen height). */ val i = Intent(this, CustomSettingsActivity::class.java) val pi = PendingIntent.getActivity(context, CONTROL_REQUEST_CODE, i, PendingIntent.FLAG_UPDATE_CURRENT) updatePublisher = ReplayProcessor.create() if (controlIds.contains(MY-UNIQUE-DEVICE-ID)) { val control = Control.StatefulBuilder(MY-UNIQUE-DEVICE-ID, pi) // Required: The name of the control .setTitle(MY-CONTROL-TITLE) // Required: Usually the room where the control is located .setSubtitle(MY -CONTROL-SUBTITLE) // Optional: Structure where the control is located, an example would be a house .setStructure(MY-CONTROL-STRUCTURE) // Required: Type of device, i.e., thermostat, light, switch .setDeviceType(DeviceTypes.DEVICE-TYPE) // For example, DeviceTypes.TYPE_THERMOSTAT // Required: Current status of the device .setStatus(Control.CURRENT-STATUS) // For example, Control.STATUS_OK .build() updatePublisher.onNext(control) } // If you have other controls, check that they have been selected here // Uses the Reactive Streams API updatePublisher.onNext(control) } }
Java
private ReplayProcessorupdatePublisher; @Override public Publisher createPublisherFor(List controlIds) { Context context = getBaseContext(); /* Fill in details for the activity related to this device. On long press, * this Intent will be launched in a bottomsheet. Please design the activity * accordingly to fit a more limited space (about 2/3 screen height). */ Intent i = new Intent(); PendingIntent pi = PendingIntent.getActivity(context, 1, i, PendingIntent.FLAG_UPDATE_CURRENT); updatePublisher = ReplayProcessor.create(); // For each controlId in controlIds if (controlIds.contains(MY-UNIQUE-DEVICE-ID)) { Control control = new Control.StatefulBuilder(MY-UNIQUE-DEVICE-ID, pi) // Required: The name of the control .setTitle(MY-CONTROL-TITLE) // Required: Usually the room where the control is located .setSubtitle(MY-CONTROL-SUBTITLE) // Optional: Structure where the control is located, an example would be a house .setStructure(MY-CONTROL-STRUCTURE) // Required: Type of device, i.e., thermostat, light, switch .setDeviceType(DeviceTypes.DEVICE-TYPE) // For example, DeviceTypes.TYPE_THERMOSTAT // Required: Current status of the device .setStatus(Control.CURRENT-STATUS) // For example, Control.STATUS_OK .build(); updatePublisher.onNext(control); } // Uses the Reactive Streams API return FlowAdapters.toFlowPublisher(updatePublisher); }
Tangani tindakan
Metode performControlAction()
memberikan sinyal bahwa pengguna telah berinteraksi dengan kontrol yang dipublikasikan. Tindakan tersebut didikte oleh jenis ControlAction
yang dikirimkan. Lakukan tindakan yang sesuai untuk kontrol yang ditentukan, lalu perbarui status perangkat di UI Android.
Kotlin
class MyCustomControlService : ControlsProviderService() { override fun performControlAction( controlId: String, action: ControlAction, consumer: Consumer) { /* First, locate the control identified by the controlId. Once it is located, you can * interpret the action appropriately for that specific device. For instance, the following * assumes that the controlId is associated with a light, and the light can be turned on * or off. */ if (action is BooleanAction) { // Inform SystemUI that the action has been received and is being processed consumer.accept(ControlAction.RESPONSE_OK) // In this example, action.getNewState() will have the requested action: true for “On”, // false for “Off”. /* This is where application logic/network requests would be invoked to update the state of * the device. * After updating, the application should use the publisher to update SystemUI with the new * state. */ Control control = Control.StatefulBuilder (MY-UNIQUE-DEVICE-ID, pi) // Required: The name of the control .setTitle(MY-CONTROL-TITLE) // Required: Usually the room where the control is located .setSubtitle(MY-CONTROL-SUBTITLE) // Optional: Structure where the control is located, an example would be a house .setStructure(MY-CONTROL-STRUCTURE) // Required: Type of device, i.e., thermostat, light, switch .setDeviceType(DeviceTypes.DEVICE-TYPE) // For example, DeviceTypes.TYPE_THERMOSTAT // Required: Current status of the device .setStatus(Control.CURRENT-STATUS) // For example, Control.STATUS_OK .build() // This is the publisher the application created during the call to createPublisherFor() updatePublisher.onNext(control) } } }
Java
@Override public void performControlAction(String controlId, ControlAction action, Consumerconsumer) { /* First, locate the control identified by the controlId. Once it is located, you can * interpret the action appropriately for that specific device. For instance, the following * assumes that the controlId is associated with a light, and the light can be turned on * or off. */ if (action instanceof BooleanAction) { // Inform SystemUI that the action has been received and is being processed consumer.accept(ControlAction.RESPONSE_OK); BooleanAction action = (BooleanAction) action; // In this example, action.getNewState() will have the requested action: true for “On”, // false for “Off”. /* This is where application logic/network requests would be invoked to update the state of * the device. * After updating, the application should use the publisher to update SystemUI with the new * state. */ Control control = new Control.StatefulBuilder(MY-UNIQUE-DEVICE-ID, pi) // Required: The name of the control .setTitle(MY-CONTROL-TITLE) // Required: Usually the room where the control is located .setSubtitle(MY-CONTROL-SUBTITLE) // Optional: Structure where the control is located, an example would be a house .setStructure(MY-CONTROL-STRUCTURE) // Required: Type of device, i.e., thermostat, light, switch .setDeviceType(DeviceTypes.DEVICE-TYPE) // For example, DeviceTypes.TYPE_THERMOSTAT // Required: Current status of the device .setStatus(Control.CURRENT-STATUS) // For example, Control.STATUS_OK .build(); // This is the publisher the application created during the call to createPublisherFor() updatePublisher.onNext(control); } }