Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Controls API Sample
package com.kieronquinn.app.controltest
import android.app.PendingIntent
import android.app.Service
import android.content.ComponentName
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.drawable.Icon
import android.os.Bundle
import android.os.IBinder
import android.provider.Settings
import android.service.controls.Control
import android.service.controls.ControlsProviderService
import android.service.controls.DeviceTypes
import android.service.controls.actions.BooleanAction
import android.service.controls.actions.ControlAction
import android.service.controls.actions.FloatAction
import android.service.controls.templates.*
import android.util.Log
import android.widget.Toast
import androidx.annotation.RequiresApi
import java.lang.reflect.Method
import java.util.concurrent.Flow
import java.util.function.Consumer
/*
Note: This was originally created before the API became public, so contains some hardcoded values rather than referring to the classes.
Please don't just copy paste this, use it to see how the API works.
*/
@RequiresApi(30)
class ControlTile : ControlsProviderService() {
private val screenBrightness: Int
get() = Settings.System.getInt(this.contentResolver, Settings.System.SCREEN_BRIGHTNESS)
private val controlList: List<Control>
get() {
val mutableList = ArrayList<Control>()
mutableList.add {
val control = Control.StatefulBuilder(
"Button",
PendingIntent.getActivity(
this,
0,
Intent(this, MainActivity::class.java),
PendingIntent.FLAG_CANCEL_CURRENT
)
)
control.setTitle("Switch Button")
control.setSubtitle("Test button")
control.setDeviceType(DeviceTypes.TYPE_GENERIC_ON_OFF)
control.setControlId("button")
control.setStatus(1)
val controlButton = ControlButton(true, "button")
control.setControlTemplate(ToggleTemplate("button", controlButton))
control.build()
}
mutableList.add {
val control = Control.StatefulBuilder(
"brightness",
PendingIntent.getActivity(
this,
0,
Intent(this, MainActivity::class.java),
PendingIntent.FLAG_CANCEL_CURRENT
)
)
control.setTitle("Brightness")
control.setSubtitle("Slider")
control.setDeviceType(DeviceTypes.TYPE_LIGHT)
control.setCustomIcon(Icon.createWithResource(this, R.drawable.ic_brightness))
control.setCustomColor(ColorStateList.valueOf(Color.RED))
control.setControlId("brightness")
control.setStatus(1)
val controlButton = ControlButton(true, "brightness")
control.setControlTemplate(
ToggleRangeTemplate(
"brightness",
controlButton,
RangeTemplate("range", 0f, 255f, screenBrightness.toFloat(), 5f, null)
)
)
control.build()
}
return mutableList
}
override fun createPublisherForAllAvailable(): Flow.Publisher<Control> {
return Flow.Publisher {
for (control in controlList) {
it.onNext(control)
}
it.onComplete()
}
}
override fun performControlAction(
controlId: String,
action: ControlAction,
consumer: Consumer<Int>
) {
if (controlId == "brightness") {
if (action is FloatAction) {
val value = action.newValue
Settings.System.putInt(
this.contentResolver,
Settings.System.SCREEN_BRIGHTNESS,
value.toInt()
)
return
} else if (action is BooleanAction) {
startActivity(Intent(Settings.ACTION_DISPLAY_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
}
}
Log.d(
"ControlTest",
"performControlAction $controlId ${action.toString()} ${consumer.toString()}"
)
Toast.makeText(this, "Clicked", Toast.LENGTH_LONG).show()
consumer.accept(ControlAction.RESPONSE_OK)
}
override fun createPublisherFor(controlIds: MutableList<String>): Flow.Publisher<Control> {
Log.d("ControlTest", "publisherFor ${controlIds.toString()}")
return Flow.Publisher {
for (control in controlList) {
if (controlIds.contains(control.controlId)) {
Log.d("ControlTest", "Found ${control.controlId}")
it.onSubscribe(object : Flow.Subscription {
override fun cancel() {
Log.d("ControlTest", "cancel")
}
override fun request(p0: Long) {
Log.d("ControlTest", "request $p0")
}
})
it.onNext(control)
}
}
}
}
private fun ArrayList<Control>.add(inline: () -> Control) {
this.add(inline.invoke())
}
}
@alexeyvasilyev

This comment has been minimized.

Copy link

@alexeyvasilyev alexeyvasilyev commented Aug 10, 2020

How it should be added to AndroidManifest.xml file?

Just adding via <service android:name="com.kieronquinn.app.controltest.ControlTile"/> does not work. Looks like there should be some intent-filter.

@alexeyvasilyev

This comment has been minimized.

Copy link

@alexeyvasilyev alexeyvasilyev commented Aug 10, 2020

Found an answer by decompiling Google Home app.

<service android:name="com.google.android.apps.chromecast.app.systemcontrol.HomeControlService" android:permission="android.permission.BIND_CONTROLS" android:exported="true">
  <intent-filter>
    <action android:name="android.service.controls.ControlsProviderService"/>
  </intent-filter>
</service>
@AndroidDeveloperLB

This comment has been minimized.

Copy link

@AndroidDeveloperLB AndroidDeveloperLB commented Sep 27, 2020

Can you please provide a full sample for it on Github? Maybe of the minimal stuff to make a tile on the power menu?

@AndroidDeveloperLB

This comment has been minimized.

Copy link

@AndroidDeveloperLB AndroidDeveloperLB commented Sep 30, 2020

Seems you have a bug on your code. Long pressing the tiles should open some Activity, and you need to add FLAG_ACTIVITY_NEW_TASK to the Intent.

@KieronQuinn

This comment has been minimized.

Copy link
Owner Author

@KieronQuinn KieronQuinn commented Sep 30, 2020

@AndroidDeveloperLB

This was meant to just be a dirty example during the previews and betas, not a full sample project. The code is out of date, launching the activity previously worked as is shown in the video on here which is running this exact code: https://www.xda-developers.com/android-11-quick-control-shortcuts-power-menu/

@AndroidDeveloperLB

This comment has been minimized.

Copy link

@AndroidDeveloperLB AndroidDeveloperLB commented Sep 30, 2020

I don't see long pressing on the tiles. Maybe on 0:19:
https://youtu.be/zhJbzt2Ts5E?t=19

But then it becomes black.
In any case, Tasker app seems to have the same issue, and as a result I also found this crash of the OS when developers don't use it right:
https://issuetracker.google.com/issues/169658959

@KieronQuinn

This comment has been minimized.

Copy link
Owner Author

@KieronQuinn KieronQuinn commented Oct 1, 2020

Yes, that is long press (there's no other interaction there that could launch anything). At that time it launched the activity in that windowed mode, but didn't work correctly, hence the black.

@AndroidDeveloperLB

This comment has been minimized.

Copy link

@AndroidDeveloperLB AndroidDeveloperLB commented Oct 4, 2020

ok

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.