Sous Android 11 et versions ultérieures, la fonctionnalité Accès rapide aux commandes des appareils permet à l'utilisateur d'afficher et de contrôler rapidement les périphériques externes tels que les lumières, les thermostats et les caméras d'une affordance de l'utilisateur dans les trois interactions le lanceur d'applications par défaut. L'OEM de l'appareil choisit le lanceur d'applications qu'il utilise. Appareil les agrégateurs (par exemple, Google Home) et les applications de fournisseurs tiers peuvent fournir des appareils à afficher dans cet espace. Cette page vous explique comment commandes de contrôle des appareils dans cet espace et les associer à votre application de contrôle.
<ph type="x-smartling-placeholder">Pour ajouter cette prise en charge, créez et déclarez un ControlsProviderService
. Créez le
compatibles avec votre application en fonction de types de commandes prédéfinis, puis créer
aux éditeurs pour ces options.
Interface utilisateur
Les appareils sont affichés sous Commandes des appareils sous forme de modèles de widgets. Cinq des widgets de contrôle d'appareil sont disponibles, comme illustré dans la figure suivante:
<ph type="x-smartling-placeholder">
<ph type="x-smartling-placeholder"> |
<ph type="x-smartling-placeholder">
<ph type="x-smartling-placeholder"> |
<ph type="x-smartling-placeholder">
<ph type="x-smartling-placeholder"> |
<ph type="x-smartling-placeholder">
<ph type="x-smartling-placeholder"> |
<ph type="x-smartling-placeholder">
<ph type="x-smartling-placeholder"> |
Touchez et maintenir un widget vous redirige vers l'application pour un contrôle plus approfondi. Vous pouvez personnaliser l'icône et la couleur de chaque widget, mais pour une expérience utilisateur optimale, utiliser l'icône et la couleur par défaut si celles-ci correspondent à l'appareil.
Créer le service
Cette section explique comment créer le
ControlsProviderService
Ce service indique à l'UI du système Android que votre appli contient des commandes de contrôle des appareils
qui s'affiche dans la zone Commandes des appareils de l'interface utilisateur Android.
L'API ControlsProviderService
suppose que vous connaissez les flux réactifs, car
défini dans le dépôt GitHub des flux réactifs
projet
et implémentés dans le flux Java 9
de commande.
L'API repose sur les concepts suivants:
- Éditeur:votre application est l'éditeur.
- Abonné:l'UI du système correspond à l'abonné et peut demander un numéro. de contrôle de l'éditeur.
- Abonnement:la période pendant laquelle l'éditeur peut envoyer des mises à jour. à l'UI du système. L'éditeur ou l'abonné peut fermer ceci fenêtre.
Déclarer le service
Votre application doit déclarer un service (par exemple, MyCustomControlService
) dans
le fichier manifeste de son application.
Le service doit inclure un filtre d'intent pour ControlsProviderService
. Ce
permet aux applications de contribuer aux commandes de l'UI du système.
Vous avez également besoin d'un label
qui s'affiche dans les commandes de l'UI du système.
L'exemple suivant montre comment déclarer un service:
<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>
Ensuite, créez un fichier Kotlin nommé MyCustomControlService.kt
et transformez-le
étendez ControlsProviderService()
:
Kotlin
class MyCustomControlService : ControlsProviderService() { ... }
Java
public class MyCustomJavaControlService extends ControlsProviderService { ... }
Sélectionnez le type de commande approprié
L'API fournit des méthodes de compilateur pour créer les commandes. Pour renseigner le champ compilateur, déterminez l'appareil que vous souhaitez contrôler et la façon dont l'utilisateur interagit avec lui. Procédez comme suit :
- Sélectionnez le type d'appareil représenté par la commande. La
La classe
DeviceTypes
est une énumération de tous les appareils compatibles Le type permet de déterminer les icônes et les couleurs de l'appareil dans l'interface utilisateur. - Déterminez le nom visible par l'utilisateur, l'emplacement de l'appareil, par exemple "cuisine" et d'autres éléments textuels de l'UI associés à la commande.
- Choisissez le meilleur modèle pour faciliter l'interaction des utilisateurs. Les sélecteurs se voient attribuer
ControlTemplate
depuis l'application. Ce modèle montre directement l'état de contrôle et les modes de saisie disponibles, c'est-à-direControlAction
Le tableau suivant présente certains des modèles disponibles et les actions à effectuer compatibles:
Template | Action | Description |
ControlTemplate.getNoTemplateObject()
|
None
|
L'application peut l'utiliser pour transmettre des informations sur la commande, mais l'utilisateur ne peut pas interagir avec. |
ToggleTemplate
|
BooleanAction
|
Représente une commande qui peut être activée ou désactivée
différents états. L'objet BooleanAction contient un champ qui change
pour représenter le nouvel état demandé lorsque l'utilisateur appuie sur la commande.
|
RangeTemplate
|
FloatAction
|
Représente un widget à curseur avec les valeurs minimale, maximale et de pas spécifiées. Quand ?
l'utilisateur interagit avec le curseur, envoyez une nouvelle FloatAction
à l'application avec la valeur mise à jour.
|
ToggleRangeTemplate
|
BooleanAction, FloatAction
|
Ce modèle combine les ToggleTemplate et
RangeTemplate Il prend en charge les événements tactiles, ainsi qu'un curseur,
par exemple pour contrôler les éclairages à intensité variable.
|
TemperatureControlTemplate
|
ModeAction, BooleanAction, FloatAction
|
En plus d'encapsuler les actions précédentes, ce modèle permet l'utilisateur a défini un mode, par exemple Chauffage, Climatisation, Chauffage/Climatisation, Éco ou Désactivé. |
StatelessTemplate
|
CommandAction
|
Utilisé pour indiquer une commande qui offre une fonctionnalité tactile, mais dont l'état par exemple, une télécommande de télévision infrarouge. Vous pouvez utiliser cette pour définir une routine ou une macro, c'est-à-dire une agrégation de variables et les changements d'état. |
Avec ces informations, vous pouvez créer la commande:
- Utilisez les
Control.StatelessBuilder
compilateur lorsque l'état du contrôle est inconnu. - Utilisez les
Control.StatefulBuilder
compilateur lorsque l'état de la commande est connu.
Par exemple, pour contrôler une ampoule connectée et un thermostat, ajoutez les éléments suivants :
constantes à votre 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; ... }
Créer des éditeurs pour les commandes
Une fois la commande créée, elle a besoin d'un éditeur. L'éditeur informe
l'interface utilisateur système de l'existence du contrôle. La classe ControlsProviderService
comporte deux méthodes d'éditeur que vous devez remplacer dans votre code d'application:
createPublisherForAllAvailable()
: crée unPublisher
pour toutes les commandes disponibles dans votre application. UtiliserControl.StatelessBuilder()
pour créer des objetsControl
pour cet éditeur.createPublisherFor()
: crée unPublisher
pour une liste de commandes données. comme identifié par leurs identifiants de chaîne. UtilisezControl.StatefulBuilder
pour créer ces objetsControl
, car l'éditeur doit attribuer un état chaque commande.
Créer l'éditeur
Lorsque votre application publie des commandes dans l'UI du système, elle ne sait pas
l'état de chaque contrôle. Obtenir l'état peut être une opération chronophage
impliquant de nombreux sauts
dans le réseau du fournisseur d’appareils. Utilisez les
createPublisherForAllAvailable()
pour annoncer au système les commandes disponibles. Cette méthode utilise
Control.StatelessBuilder
, car l'état de chaque commande est
inconnu.
Une fois les commandes affichées dans l'interface utilisateur Android , l'utilisateur peut sélectionner un favori. .
Pour utiliser des coroutines Kotlin afin de créer un ControlsProviderService
, ajoutez un
la dépendance à votre 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") }
Une fois vos fichiers Gradle synchronisés, ajoutez l'extrait suivant à votre Service
pour
Implémentez createPublisherForAllAvailable()
:
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); } }
Balayez l'écran vers le bas dans le menu système, puis repérez le bouton Commandes de l'appareil, affiché dans figure 4:
Appuyez sur Commandes de l'appareil pour accéder à un deuxième écran dans lequel vous pouvez sélectionner votre application. Une fois que vous avez sélectionné votre application, vous voyez comment l'extrait précédent crée un menu système personnalisé affichant vos nouvelles commandes, comme illustré dans la figure 5:
Implémentez maintenant la méthode createPublisherFor()
en ajoutant ce qui suit à votre
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.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(); }
Dans cet exemple, la méthode createPublisherFor()
contient un faux
de ce que votre application doit faire: communiquer avec votre appareil
récupérer son état et l'émettre au système.
La méthode createPublisherFor()
utilise des coroutines et des flux Kotlin pour satisfaire
l'API Reactive Streams requise en procédant comme suit:
- Crée un
Flow
. - Attend une seconde.
- Crée et émet l'état de l'ampoule connectée.
- Attend encore une seconde.
- Crée et émet l'état du thermostat.
Gérer les actions
La méthode performControlAction()
signale quand l'utilisateur interagit avec un
un contrôle publié. Le type d'ControlAction
envoyé détermine l'action.
Effectuer l'action appropriée pour la commande donnée, puis mettre à jour l'état
de l'appareil dans l'UI Android.
Pour terminer l'exemple, ajoutez ce qui suit à votre 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()); } }
Exécutez l'application, accédez au menu Device Controls (Commandes de contrôle des appareils) et observez le voyant ainsi que les commandes du thermostat.