Android 11 以降のクイック アクセス デバイス コントロール機能 ユーザーは、照明、カメラ、音声、動画などの外部デバイスをすばやく表示して操作できます。 3 つのインタラクションの範囲内で、ユーザー アフォーダンスから あります。デバイスの OEM は、使用するランチャーを選択します。デバイス アグリゲータ(Google Home など)とサードパーティ ベンダーのアプリは、 このスペースに表示するデバイスを提供します。このページでは、Google Meet を使用して コントロール アプリにリンクすることもできます。
<ph type="x-smartling-placeholder">このサポートを追加するには、ControlsProviderService
を作成して宣言します。作成
あらかじめ定義されたコントロール タイプに基づいて、アプリがサポートしているコントロールを作成し、
できます。
ユーザー インターフェース
デバイスは、[デバイス コントロール] の下にテンプレート化されたウィジェットとして表示されます。5 本 さまざまなデバイス コントロール ウィジェットを利用できます。
![]() |
![]() |
<ph type="x-smartling-placeholder">![]() |
![]() |
![]() |
接触とウィジェットを長押しするとアプリに移動し、より細かい操作が可能です。Google Chat では 各ウィジェットでアイコンや色をカスタマイズできますが デフォルト セットがデバイスと一致する場合は、デフォルトのアイコンと色を使用します。
![温度パネルのウィジェットを示す画像(開いている)](https://developer.android.com/static/images/device-control/device-control-panel2.png?hl=ja)
サービスを作成する
このセクションでは、Terraform を使用して
ControlsProviderService
。
このサービスは、アプリにデバイス コントロールが含まれていることを Android システム UI に通知します。
Android UI の [デバイス コントロール] 領域に表示する必要があります。
ControlsProviderService
API は、次のようなリアクティブ ストリームに精通していることを前提としています。
Reactive Streams GitHub で定義される
プロジェクト
Java 9 Flow に実装
インターフェースをご覧ください。
この API は、次のコンセプトに基づいて構築されています。
- パブリッシャー: アプリケーションがパブリッシャーです。
- サブスクライバー: システム UI はサブスクライバーであり、番号をリクエストできます。 さまざまな設定を行えます
- 購読: ニュース メディアが更新情報を送信できる期間 システム UI に表示されます。パブリッシャーまたはサブスクライバーは、この クリックします。
サービスを宣言する
アプリは、MyCustomControlService
などのサービスを宣言する必要があります。
追加します。
サービスには ControlsProviderService
のインテント フィルタが含まれている必要があります。この
フィルタを使用すると、アプリがシステム UI にコントロールを提供できるようになります。
また、システム UI のコントロールに表示される 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 { ... }
正しいコントロール タイプを選択する
API には、コントロールを作成するためのビルダー メソッドが用意されています。ルールに値を入力するには、 制御するデバイスとユーザーの操作を決定します。 できます。次の手順を行います。
- コントロールが表すデバイスのタイプを選択します。「
DeviceTypes
クラスは すべてのサポート対象デバイスの列挙です。種類は、変数の種類と UI でデバイスのアイコンと色を変更できます。 - ユーザー向けの名前、デバイスの位置情報(例: コントロールに関連付けられているその他の UI テキスト要素などです。
- ユーザー インタラクションをサポートする最適なテンプレートを選択します。コントロールには、アプリケーションから
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; ... }
コントロール用のパブリッシャーを作成する
コントロールを作成したら、次はパブリッシャーが必要です。パブリッシャーは、システム UI にコントロールの存在を通知します。ControlsProviderService
クラスには、アプリケーション コードでオーバーライドする必要がある 2 つのパブリッシャー メソッドがあります。
createPublisherForAllAvailable()
: 環境変数を作成します。Publisher
をご覧ください。Control.StatelessBuilder()
を使用する このパブリッシャーのControl
オブジェクトを作成します。createPublisherFor()
: 指定されたコントロールのリストのPublisher
を作成します。 文字列識別子によって識別されます。Control.StatefulBuilder
の用途 これらのControl
オブジェクトを作成します。これは、パブリッシャーが状態をオブジェクトに割り当てる必要があるためです。 できます。
パブリッシャーを作成する
アプリが最初にシステム UI にコントロールを公開したとき、
状態を定義します。状態の取得には時間がかかる可能性がある
デバイス プロバイダのネットワークで多数のホップが関係しています。createPublisherForAllAvailable()
メソッドを使用して、使用可能なコントロールをシステムにアドバタイズします。このメソッドでは、
Control.StatelessBuilder
ビルダークラス(各コントロールの状態は
不明。
コントロールが Android UI に表示されたら、ユーザーはお気に入りを選択できるようになります。 できます。
Kotlin コルーチンを使用して ControlsProviderService
を作成するには、新しい
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 ファイルを同期したら、次のスニペットを Service
に追加して、
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); } }
システム メニューを下にスワイプして、[デバイス コントロール] ボタン( 図 4:
![デバイス コントロールのシステム UI を示す画像](https://developer.android.com/static/images/ui/device_controls_ui.png?hl=ja)
[デバイス コントロール] をタップすると、2 つ目の画面に移動し、 説明します。アプリを選択すると、前のスニペットによって 新しいコントロールを表示するカスタム システム メニュー(図 5 を参照)
![照明とサーモスタットのコントロールを含むシステム メニューを示す画像](https://developer.android.com/static/images/ui/device_controls_example_1.png?hl=ja)
次に、createPublisherFor()
メソッドを実装して、
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(); }
この例では、createPublisherFor()
メソッドに架空の
アプリがやるべきことの実装方法。つまり、デバイスと通信し、
そのステータスを取得してシステムに出力できます。
createPublisherFor()
メソッドは Kotlin のコルーチンと Flow を使用して、
必要な Reactive Streams API を確認します。手順は次のとおりです。
Flow
を作成します。- 1 秒待ちます。
- スマートライトの状態を作成して出力します。
- さらに 1 秒待ちます。
- サーモスタットの状態を作成して出力します。
アクションを処理する
performControlAction()
メソッドは、ユーザーが
あります。送信される ControlAction
のタイプによってアクションが決まります。
指定されたコントロールに対して適切なアクションを実行し、状態を更新する
デバイスの Android UI で表示できます。
この例を完了するには、次のコードを 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()); } }
アプリを実行し、[デバイス コントロール] メニューにアクセスして サーモスタットの制御。
![照明とサーモスタットの制御を示す画像](https://developer.android.com/static/images/ui/device_controls_example_2.png?hl=ja)