Android 11 और उसके बाद के वर्शन में, डिवाइस के कंट्रोल को तुरंत ऐक्सेस करने की सुविधा की मदद से, उपयोगकर्ता डिफ़ॉल्ट लॉन्चर से तीन इंटरैक्शन में, लाइट, थर्मोस्टैट, और कैमरे जैसे बाहरी डिवाइसों को तुरंत देख और कंट्रोल कर सकता है. डिवाइस का OEM यह चुनता है कि कौनसा लॉन्चर इस्तेमाल करना है. डिवाइस एग्रीगेटर, जैसे कि Google Home और तीसरे पक्ष के वेंडर ऐप्लिकेशन, इस स्पेस में डिवाइसों को दिखा सकते हैं. इस पेज पर, इस स्पेस में डिवाइस के कंट्रोल दिखाने और उन्हें कंट्रोल ऐप्लिकेशन से लिंक करने का तरीका बताया गया है.
इस सुविधा को जोड़ने के लिए, ControlsProviderService
बनाएं और उसका एलान करें. पहले से तय कंट्रोल टाइप के आधार पर, ऐसे कंट्रोल बनाएं जिनका इस्तेमाल आपके ऐप्लिकेशन में किया जा सकता है. इसके बाद, इन कंट्रोल के लिए पब्लिशर बनाएं.
उपयोगकर्ता इंटरफ़ेस
डिवाइस, डिवाइस कंट्रोल में टेंप्लेट वाले विजेट के तौर पर दिखते हैं. डिवाइस कंट्रोल करने वाले पांच विजेट उपलब्ध हैं, जैसा कि इस इमेज में दिखाया गया है:
|
|
|
|
|
किसी विजेट को दबाकर रखने पर, आपको ऐप्लिकेशन पर ले जाया जाता है, ताकि आप उसे बेहतर तरीके से कंट्रोल कर सकें. हर विजेट के आइकॉन और रंग को अपनी पसंद के मुताबिक बनाया जा सकता है. हालांकि, बेहतर उपयोगकर्ता अनुभव के लिए, डिफ़ॉल्ट आइकॉन और रंग का इस्तेमाल करें. ऐसा तब करें, जब डिफ़ॉल्ट सेट डिवाइस से मेल खाता हो.
सेवा बनाना
इस सेक्शन में, ControlsProviderService
बनाने का तरीका बताया गया है.
यह सेवा, Android सिस्टम यूज़र इंटरफ़ेस (यूआई) को बताती है कि आपके ऐप्लिकेशन में डिवाइस कंट्रोल मौजूद हैं. इन्हें Android यूज़र इंटरफ़ेस के डिवाइस कंट्रोल सेक्शन में दिखाया जाना चाहिए.
ControlsProviderService
API, रिऐक्टिव स्ट्रीम के बारे में अच्छी तरह से समझ लेता है.
जैसा कि Reactive Streams GitHub
प्रोजेक्ट में बताया गया है
और Java 9 फ़्लो
इंटरफ़ेस में लागू किया गया है.
एपीआई को इन कॉन्सेप्ट के आधार पर बनाया गया है:
- प्रकाशक: आपका ऐप्लिकेशन, प्रकाशक है.
- सदस्य: सिस्टम यूज़र इंटरफ़ेस (यूआई), सदस्य होता है. यह पब्लिशर से कई तरह के कंट्रोल का अनुरोध कर सकता है.
- सदस्यता: वह समयसीमा जिसके दौरान पब्लिशर, सिस्टम यूज़र इंटरफ़ेस (यूआई) में अपडेट भेज सकता है. पब्लिशर या सदस्य, दोनों में से कोई भी इस विंडो को बंद कर सकता है.
सेवा का एलान करना
आपके ऐप्लिकेशन के मेनिफ़ेस्ट में, MyCustomControlService
जैसी सेवा के बारे में जानकारी होनी चाहिए.
सेवा में ControlsProviderService
के लिए इंटेंट फ़िल्टर शामिल होना चाहिए. इस फ़िल्टर की मदद से, ऐप्लिकेशन सिस्टम यूज़र इंटरफ़ेस (यूआई) में कंट्रोल जोड़ सकते हैं.
आपको label
की भी ज़रूरत होगी, जो सिस्टम यूज़र इंटरफ़ेस (यूआई) के कंट्रोल में दिखता है.
यहां दिए गए उदाहरण में, किसी सेवा का एलान करने का तरीका बताया गया है:
<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>
इसके बाद, MyCustomControlService.kt
नाम से एक नई Kotlin फ़ाइल बनाएं और इसे ControlsProviderService()
से एक्सटेंड़ करें:
Kotlin
class MyCustomControlService : ControlsProviderService() { ... }
Java
public class MyCustomJavaControlService extends ControlsProviderService { ... }
कंट्रोल का सही टाइप चुनना
एपीआई, कंट्रोल बनाने के लिए बिल्डर के तरीके उपलब्ध कराता है. बिल्डर में जानकारी अपने-आप भरने के लिए, वह डिवाइस तय करें जिसे आपको कंट्रोल करना है. साथ ही, यह भी तय करें कि उपयोगकर्ता इससे कैसे इंटरैक्ट करे. यह तरीका अपनाएं:
- कंट्रोल से जिस तरह का डिवाइस दिखाया जा रहा है उसे चुनें.
DeviceTypes
क्लास, उन सभी डिवाइसों की सूची है जिन पर यह सुविधा काम करती है. टाइप का इस्तेमाल, यूज़र इंटरफ़ेस (यूआई) में डिवाइस के आइकॉन और रंग तय करने के लिए किया जाता है. - उपयोगकर्ता के सामने दिखने वाला नाम, डिवाइस की जगह की जानकारी—उदाहरण के लिए, किचन—और कंट्रोल से जुड़े अन्य यूज़र इंटरफ़ेस (यूआई) टेक्स्ट एलिमेंट तय करें.
- उपयोगकर्ता के इंटरैक्शन के लिए सबसे अच्छा टेंप्लेट चुनें. ऐप्लिकेशन से कंट्रोल को
ControlTemplate
असाइन किया जाता है. यह टेंप्लेट, सीधे उपयोगकर्ता को कंट्रोल की स्थिति और इनपुट के उपलब्ध तरीके दिखाता है. जैसे,ControlAction
. नीचे दी गई टेबल में कुछ उपलब्ध टेंप्लेट और उनमें इस्तेमाल की जा सकने वाली कार्रवाइयां की जानकारी दी गई है:
टेंप्लेट | कार्रवाई | ब्यौरा |
ControlTemplate.getNoTemplateObject()
|
None
|
ऐप्लिकेशन, कंट्रोल के बारे में जानकारी देने के लिए इसका इस्तेमाल कर सकता है, लेकिन उपयोगकर्ता इसका इस्तेमाल नहीं कर सकता. |
ToggleTemplate
|
BooleanAction
|
यह ऐसे कंट्रोल को दिखाता है जिसे चालू और बंद की स्थितियों के बीच स्विच किया जा सकता है. BooleanAction ऑब्जेक्ट में एक फ़ील्ड होता है, जो उपयोगकर्ता के कंट्रोल पर टैप करने पर, अनुरोध की गई नई स्थिति दिखाने के लिए बदल जाता है.
|
RangeTemplate
|
FloatAction
|
यह स्लाइडर विजेट दिखाता है, जिसमें कम से कम, ज़्यादा से ज़्यादा, और चरण की वैल्यू तय की गई है. जब उपयोगकर्ता स्लाइडर के साथ इंटरैक्ट करता है, तो अपडेट की गई वैल्यू के साथ, ऐप्लिकेशन को एक नया FloatAction ऑब्जेक्ट भेजें.
|
ToggleRangeTemplate
|
BooleanAction, FloatAction
|
यह टेंप्लेट, ToggleTemplate और
RangeTemplate का कॉम्बिनेशन है. यह टच इवेंट के साथ-साथ स्लाइडर के साथ भी काम करता है. उदाहरण के लिए, रोशनी को कम या ज़्यादा करने के लिए.
|
TemperatureControlTemplate
|
ModeAction, BooleanAction, FloatAction
|
इस टेंप्लेट में, पिछली कार्रवाइयों को शामिल करने के साथ-साथ, उपयोगकर्ता को कोई मोड सेट करने की सुविधा भी मिलती है. जैसे, गर्म, ठंडा, गर्म/ठंडा, ईको या बंद. |
StatelessTemplate
|
CommandAction
|
इसका इस्तेमाल ऐसे कंट्रोल के बारे में बताने के लिए किया जाता है जो टच की क्षमता देता है, लेकिन जिसकी स्थिति का पता नहीं चलता. जैसे, आईआर टेलीविज़न रिमोट. इस टेंप्लेट का इस्तेमाल, किसी रूटीन या मैक्रो को तय करने के लिए किया जा सकता है. यह कंट्रोल और स्टेटस में हुए बदलावों का एग्रीगेशन होता है. |
इस जानकारी की मदद से, यह कंट्रोल बनाया जा सकता है:
- कंट्रोल की स्थिति के बारे में जानकारी न होने पर,
Control.StatelessBuilder
बिल्डर क्लास का इस्तेमाल करें. - जब कंट्रोल की स्थिति पता हो, तो
Control.StatefulBuilder
बिल्डर क्लास का इस्तेमाल करें.
उदाहरण के लिए, स्मार्ट लाइट बल्ब और थर्मोस्टैट को कंट्रोल करने के लिए, अपने MyCustomControlService
में ये
कॉन्सटेंट जोड़ें:
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; ... }
कंट्रोल के लिए पब्लिशर बनाएं
कंट्रोल बनाने के बाद, उसे पब्लिशर की ज़रूरत होती है. पब्लिशर, सिस्टम यूज़र इंटरफ़ेस (यूआई) को कंट्रोल के मौजूद होने की जानकारी देता है. ControlsProviderService
क्लास में, पब्लिशर के दो तरीके होते हैं. आपको अपने ऐप्लिकेशन कोड में इन तरीकों को बदलना होगा:
createPublisherForAllAvailable()
: यह आपके ऐप्लिकेशन में उपलब्ध सभी कंट्रोल के लिए,Publisher
बनाता है. इस पब्लिशर के लिएControl
ऑब्जेक्ट बनाने के लिए,Control.StatelessBuilder()
का इस्तेमाल करें.createPublisherFor()
: दिए गए कंट्रोल की सूची के लिएPublisher
बनाता है, जैसा कि उनके स्ट्रिंग आइडेंटिफ़ायर से पता चलता है. इनControl
ऑब्जेक्ट को बनाने के लिएControl.StatefulBuilder
का इस्तेमाल करें, क्योंकि पब्लिशर को हर कंट्रोल के लिए एक स्टेटस असाइन करना होगा.
पब्लिशर बनाएं
जब आपका ऐप्लिकेशन पहली बार सिस्टम यूज़र इंटरफ़ेस (यूआई) में कंट्रोल पब्लिश करता है, तो ऐप्लिकेशन को हर कंट्रोल की स्थिति के बारे में पता नहीं होता. इस स्थिति का पता लगाने में काफ़ी समय लगता है.
इसमें डिवाइस देने वाली कंपनी के नेटवर्क में कई हॉप शामिल होते हैं. सिस्टम के लिए उपलब्ध कंट्रोल का विज्ञापन देने के लिए, createPublisherForAllAvailable()
तरीके का इस्तेमाल करें. यह तरीका Control.StatelessBuilder
बिल्डर क्लास का इस्तेमाल करता है, क्योंकि हर कंट्रोल की स्थिति पता नहीं होती.
Android यूज़र इंटरफ़ेस (यूआई) में कंट्रोल दिखने के बाद , उपयोगकर्ता अपने पसंदीदा कंट्रोल चुन सकता है.
ControlsProviderService
बनाने के लिए Kotlin कोरूटीन इस्तेमाल करने के लिए, अपने build.gradle
पर एक नई डिपेंडेंसी जोड़ें:
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 फ़ाइलों को सिंक करने के बाद, createPublisherForAllAvailable()
को लागू करने के लिए, अपने Service
में यह स्निपेट जोड़ें:
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); } }
सिस्टम मेन्यू को नीचे की ओर स्वाइप करें और डिवाइस कंट्रोल बटन ढूंढें. यह बटन चौथे चित्र में दिखाया गया है:
डिवाइस कंट्रोल पर टैप करने से, आपको दूसरी स्क्रीन पर ले जाया जाता है. यहां अपना ऐप्लिकेशन चुना जा सकता है. ऐप्लिकेशन चुनने के बाद, आपको पता चलता है कि पिछले स्निपेट ने आपके नए कंट्रोल दिखाने के लिए, कस्टम सिस्टम मेन्यू कैसे बनाया है. इसकी जानकारी, पांचवें चित्र में दी गई है:
अब createPublisherFor()
तरीके को लागू करें. इसके लिए, अपने 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.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(); }
इस उदाहरण में, createPublisherFor()
तरीके में बताया गया है कि आपके ऐप्लिकेशन को क्या करना चाहिए: स्टेटस वापस पाने के लिए, अपने डिवाइस से संपर्क करें और उस स्टेटस को सिस्टम को भेजें.
createPublisherFor()
तरीका, Kotlin कोरूटीन और फ़्लो का इस्तेमाल करके, ज़रूरी Reactive Streams API की शर्तों को पूरा करता है. इसके लिए, यह तरीका ये काम करता है:
Flow
बनाता है.- एक सेकंड इंतज़ार करें.
- स्मार्ट लाइट की स्थिति बनाता और उसे दिखाता है.
- एक और सेकंड इंतज़ार करता है.
- इससे थर्मोस्टैट की स्थिति बनती है और आगे बढ़ती है.
कार्रवाइयां मैनेज करना
performControlAction()
तरीका तब सिग्नल भेजता है, जब उपयोगकर्ता किसी पब्लिश किए गए कंट्रोल से इंटरैक्ट करता है. भेजे गए ControlAction
के टाइप से कार्रवाई तय होती है.
दिए गए कंट्रोल के लिए सही कार्रवाई करें. इसके बाद, Android यूज़र इंटरफ़ेस (यूआई) में डिवाइस की स्थिति अपडेट करें.
उदाहरण को पूरा करने के लिए, अपने Service
में यह जानकारी जोड़ें:
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()); } }
ऐप्लिकेशन चलाएं, डिवाइस कंट्रोल मेन्यू ऐक्सेस करें, और अपनी लाइट और थर्मोस्टैट कंट्रोल देखें.