Skip to content

Instantly share code, notes, and snippets.

@KieronQuinn
Created June 11, 2020 19:46
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save KieronQuinn/c9950f3ee09e11f305ce16e7f48f03b8 to your computer and use it in GitHub Desktop.
Save KieronQuinn/c9950f3ee09e11f305ce16e7f48f03b8 to your computer and use it in GitHub Desktop.
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
Copy link

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
Copy link

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
Copy link

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
Copy link

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
Copy link
Author

@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
Copy link

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
Copy link
Author

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
Copy link

ok

@renattele
Copy link

Hi! In your example not working switching between on and off on switch button(background not switching). Do you know how to improve this issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment