@@ -7,144 +7,286 @@ import android.content.Context
77import java.lang.Class.forName
88import java.lang.reflect.Method
99
10+ // ─── Constants mirroring FrontLightController / BaseBrightnessProvider ────────
11+
12+ private const val LIGHT_TYPE_FL = 1 // FLBrightnessProvider — single channel
13+ private const val LIGHT_TYPE_CTM_WARM = 2 // WarmBrightnessProvider — warm channel
14+ private const val LIGHT_TYPE_CTM_COLD = 3 // ColdBrightnessProvider — cold channel
15+ private const val LIGHT_TYPE_CTM_ALL = 4 // open/close the whole CTM unit
16+ private const val LIGHT_TYPE_TEMP = 6 // CTMTemperatureProvider — CTM warmth param
17+ private const val LIGHT_TYPE_CTM_BR = 7 // CTMBrightnessProvider — CTM brightness param
18+
19+ // Mirrors BrightnessType enum
20+ enum class OnyxBrightnessType { FL , WARM_AND_COLD , CTM , NONE }
21+
22+ // ─── Controller ───────────────────────────────────────────────────────────────
23+
1024class OnyxSdkLightsController : LightsInterface {
25+
1126 companion object {
1227 private const val TAG = " Lights"
13- private const val BRIGHTNESS_MAX = 255
14- private const val WARMTH_MAX = 255
1528 private const val MIN = 0
29+ private const val FALLBACK_MAX = 255
1630 }
1731
18- override fun getPlatform (): String {
19- return " onyx-sdk-lights"
20- }
21-
22- override fun hasFallback (): Boolean {
23- return false
24- }
32+ override fun getPlatform (): String = " onyx-sdk-lights"
33+ override fun hasFallback (): Boolean = false
34+ override fun needsPermission (): Boolean = false
35+ override fun hasStandaloneWarmth (): Boolean = false
2536
2637 override fun hasWarmth (): Boolean {
27- return true
38+ // We can't easily re-init here without Activity/Context,
39+ // but we can trust the previous detection if it found warmth.
40+ return when (OnyxDevice .brightnessType) {
41+ OnyxBrightnessType .WARM_AND_COLD ,
42+ OnyxBrightnessType .CTM -> true
43+ else -> ! OnyxDevice .isInitialized // optimistic before init
44+ }
2845 }
2946
30- override fun needsPermission (): Boolean {
31- return false
32- }
47+ // ── Read ──────────────────────────────────────────────────────────────────
3348
3449 override fun getBrightness (activity : Activity ): Int {
35- return FrontLight .getCold(activity)
50+ OnyxDevice .init (activity)
51+ return when (OnyxDevice .brightnessType) {
52+ OnyxBrightnessType .FL -> OnyxDevice .getLightValue(activity, LIGHT_TYPE_FL ) ? : 0
53+ OnyxBrightnessType .WARM_AND_COLD -> OnyxDevice .getLightValue(activity, LIGHT_TYPE_CTM_COLD ) ? : 0
54+ OnyxBrightnessType .CTM -> OnyxDevice .getLightValue(activity, LIGHT_TYPE_CTM_BR ) ? : 0
55+ OnyxBrightnessType .NONE -> 0
56+ }
3657 }
3758
3859 override fun getWarmth (activity : Activity ): Int {
39- return FrontLight .getWarm(activity)
60+ OnyxDevice .init (activity)
61+ return when (OnyxDevice .brightnessType) {
62+ OnyxBrightnessType .WARM_AND_COLD -> OnyxDevice .getLightValue(activity, LIGHT_TYPE_CTM_WARM ) ? : 0
63+ OnyxBrightnessType .CTM -> OnyxDevice .getLightValue(activity, LIGHT_TYPE_TEMP ) ? : 0
64+ else -> 0
65+ }
4066 }
4167
68+ // ── Write ─────────────────────────────────────────────────────────────────
69+
4270 override fun setBrightness (activity : Activity , brightness : Int ) {
43- if (brightness < MIN || brightness > BRIGHTNESS_MAX ) {
44- Log .w(TAG , " brightness value of of range: $brightness " )
71+ OnyxDevice .init (activity)
72+ val max = getMaxBrightness()
73+ if (brightness < MIN || brightness > max) {
74+ Log .w(TAG , " brightness out of range: $brightness (max=$max )" )
4575 return
4676 }
47- Log .v(TAG , " Setting brightness to $brightness " )
48- FrontLight .setCold(brightness, activity)
77+ Log .v(TAG , " setBrightness=$brightness type=${OnyxDevice .brightnessType} " )
78+ when (OnyxDevice .brightnessType) {
79+ OnyxBrightnessType .FL ->
80+ OnyxDevice .setLight(activity, LIGHT_TYPE_FL , brightness)
81+ OnyxBrightnessType .WARM_AND_COLD ->
82+ OnyxDevice .setLight(activity, LIGHT_TYPE_CTM_COLD , brightness)
83+ OnyxBrightnessType .CTM ->
84+ OnyxDevice .setLight(activity, LIGHT_TYPE_CTM_BR , brightness)
85+ OnyxBrightnessType .NONE -> Unit
86+ }
4987 }
5088
5189 override fun setWarmth (activity : Activity , warmth : Int ) {
52- if (warmth < MIN || warmth > WARMTH_MAX ) {
53- Log .w(TAG , " warmth value of of range: $warmth " )
90+ OnyxDevice .init (activity)
91+ val max = getMaxWarmth()
92+ if (warmth < MIN || warmth > max) {
93+ Log .w(TAG , " warmth out of range: $warmth (max=$max )" )
5494 return
5595 }
56- Log .v(TAG , " Setting warmth to $warmth " )
57- FrontLight .setWarm(warmth, activity)
96+ Log .v(TAG , " setWarmth=$warmth type=${OnyxDevice .brightnessType} " )
97+ when (OnyxDevice .brightnessType) {
98+ OnyxBrightnessType .WARM_AND_COLD ->
99+ OnyxDevice .setLight(activity, LIGHT_TYPE_CTM_WARM , warmth)
100+ OnyxBrightnessType .CTM ->
101+ OnyxDevice .setLight(activity, LIGHT_TYPE_TEMP , warmth)
102+ else -> Unit
103+ }
58104 }
59105
60- override fun getMinWarmth (): Int {
61- return MIN
62- }
106+ // ── Range ─────────────────────────────────────────────────────────────────
63107
64- override fun getMaxWarmth (): Int {
65- return WARMTH_MAX
66- }
108+ override fun getMinBrightness (): Int = MIN
109+ override fun getMinWarmth (): Int = MIN
67110
68- override fun getMinBrightness (): Int {
69- return MIN
111+ override fun getMaxBrightness (): Int = when (OnyxDevice .brightnessType) {
112+ OnyxBrightnessType .FL -> OnyxDevice .getMaxLightValue(LIGHT_TYPE_FL ) ? : FALLBACK_MAX
113+ OnyxBrightnessType .WARM_AND_COLD -> OnyxDevice .getMaxLightValue(LIGHT_TYPE_CTM_COLD ) ? : FALLBACK_MAX
114+ OnyxBrightnessType .CTM -> OnyxDevice .getMaxLightValue(LIGHT_TYPE_CTM_BR ) ? : FALLBACK_MAX
115+ OnyxBrightnessType .NONE -> FALLBACK_MAX
70116 }
71117
72- override fun getMaxBrightness (): Int {
73- return BRIGHTNESS_MAX
118+ override fun getMaxWarmth (): Int = when (OnyxDevice .brightnessType) {
119+ OnyxBrightnessType .WARM_AND_COLD -> OnyxDevice .getMaxLightValue(LIGHT_TYPE_CTM_WARM ) ? : FALLBACK_MAX
120+ OnyxBrightnessType .CTM -> OnyxDevice .getMaxLightValue(LIGHT_TYPE_TEMP ) ? : FALLBACK_MAX
121+ else -> FALLBACK_MAX
74122 }
75123
76- override fun enableFrontlightSwitch (activity : Activity ): Int {
77- return 1
78- }
79-
80- override fun hasStandaloneWarmth (): Boolean {
81- return false
82- }
124+ override fun enableFrontlightSwitch (activity : Activity ): Int = 1
83125}
84126
85- object FrontLight {
86- private const val TAG = " lights"
127+ // ─── Low-level SDK bridge ─────────────────────────────────────────────────────
87128
88- private val flController: Class <* >? = try {
89- forName(" android.onyx.hardware.DeviceController" )
90- } catch (e: Exception ) {
91- Log .w(TAG , " $e " )
92- null
93- }
129+ object OnyxDevice {
130+ private const val TAG = " OnyxDevice"
94131
95- private val setWarmBrightness : Method ? = try {
96- flController !! .getMethod( " setWarmLightDeviceValue " , Context :: class .java, Integer . TYPE )
132+ private val controller : Class < * > ? = try {
133+ Class .forName( " android.onyx.hardware.DeviceController " )
97134 } catch (e: Exception ) {
98- Log .w(TAG , " $e " )
99- null
135+ Log .w(TAG , " DeviceController not found: $e " ); null
100136 }
101- private val setColdBrightness: Method ? = try {
102- flController!! .getMethod(" setColdLightDeviceValue" , Context ::class .java, Integer .TYPE )
103- } catch (e: Exception ) {
104- Log .w(TAG , " $e " )
105- null
137+
138+ // Integer getLightValue(int type)
139+ private val mGetLightValue: Method ? = method(" getLightValue" , Integer .TYPE )
140+ ? : method(" getLightValues" , Integer .TYPE )
141+ // Integer getMaxLightValue(int type)
142+ private val mGetMaxLightValue: Method ? = method(" getMaxLightValue" , Integer .TYPE )
143+ ? : method(" getMaxLightValues" , Integer .TYPE )
144+ // boolean setLightValue(int type, int value)
145+ private val mSetLightValue: Method ? = method(" setLightValue" , Integer .TYPE , Integer .TYPE )
146+ ? : method(" setLightValues" , Integer .TYPE , Integer .TYPE )
147+ // boolean openFrontLight(int type)
148+ private val mOpenFrontLight: Method ? = method(" openFrontLight" , Integer .TYPE )
149+ // boolean closeFrontLight(int type)
150+ private val mCloseFrontLight: Method ? = method(" closeFrontLight" , Integer .TYPE )
151+ // boolean isLightOn(int type)
152+ private val mIsLightOn: Method ? = method(" isLightOn" , Context ::class .java, Integer .TYPE )
153+ ? : method(" isLightOn" , Integer .TYPE )
154+ // boolean hasFLBrightness(Context)
155+ private val mHasFLBrightness: Method ? = method(" hasFLBrightness" , Context ::class .java)
156+ // boolean hasCTMBrightness(Context)
157+ private val mHasCTMBrightness: Method ? = method(" hasCTMBrightness" , Context ::class .java)
158+ // boolean checkCTM()
159+ private val mCheckCTM: Method ? = method(" checkCTM" )
160+
161+ var brightnessType: OnyxBrightnessType = OnyxBrightnessType .NONE
162+ private set
163+
164+ var isInitialized = false
165+ private set
166+
167+ /* *
168+ * Call once from Activity.onCreate.
169+ * Mirrors BrightnessController.initProviderMap() priority order:
170+ * checkCTM() → CTM (open/close via type 4, read/write via types 6+7)
171+ * hasCTMBrightness → WARM_AND_COLD (types 2+3)
172+ * hasFLBrightness → FL (type 1)
173+ */
174+ fun init (context : Context ) {
175+ // Allow re-initialization if we are currently in a state without warmth,
176+ // to handle cases where detection might be state-dependent (like "OFF" vs "Custom" mode).
177+ if (isInitialized && brightnessType != OnyxBrightnessType .NONE && brightnessType != OnyxBrightnessType .FL ) return
178+
179+ val checkCTM = mCheckCTM?.invoke(controller) as ? Boolean ? : false
180+ val hasFL = mHasFLBrightness?.invoke(controller, context) as ? Boolean ? : false
181+ val hasCTM = mHasCTMBrightness?.invoke(controller, context) as ? Boolean ? : false
182+
183+ // Check actual hardware channel availability as fallback/confirmation
184+ val maxCTM = getMaxLightValue(LIGHT_TYPE_TEMP ) ? : 0
185+ val maxWarm = getMaxLightValue(LIGHT_TYPE_CTM_WARM ) ? : 0
186+
187+ val oldType = brightnessType
188+ brightnessType = when {
189+ checkCTM || maxCTM > 0 -> OnyxBrightnessType .CTM
190+ hasCTM || maxWarm > 0 -> OnyxBrightnessType .WARM_AND_COLD
191+ hasFL -> OnyxBrightnessType .FL
192+ else -> OnyxBrightnessType .NONE
193+ }
194+
195+ if (brightnessType != OnyxBrightnessType .NONE ) {
196+ isInitialized = true
197+ }
198+
199+ if (oldType != brightnessType) {
200+ Log .d(TAG , " Detection: checkCTM=$checkCTM hasCTM=$hasCTM hasFL=$hasFL maxCTM=$maxCTM maxWarm=$maxWarm → $brightnessType " )
201+ }
106202 }
107203
108- private val getCoolWarmBrightness: Method ? = try {
109- flController!! .getMethod(" getBrightnessConfig" , Context ::class .java, Integer .TYPE )
110- } catch (e: Exception ) {
111- Log .w(TAG , " $e " )
112- null
204+ // ── Reads ─────────────────────────────────────────────────────────────────
205+
206+ fun getLightValue (context : Context , type : Int ): Int? = mGetLightValue?.invoke(controller, type) as ? Int
207+ fun getMaxLightValue (type : Int ): Int? {
208+ val max = mGetMaxLightValue?.invoke(controller, type) as ? Number
209+ return if (max == null || max.toInt() == 0 ) null else max.toInt()
113210 }
114- private const val BRIGHTNESS_CONFIG_WARM_IDX : Int = 2
115- private const val BRIGHTNESS_CONFIG_COLD_IDX : Int = 3
116-
117- fun getWarm (context : Context ? ): Int {
118- return try {
119- getCoolWarmBrightness!! .invoke(flController!! , context, BRIGHTNESS_CONFIG_WARM_IDX ) as Int
120- } catch (e: Exception ) {
121- e.printStackTrace()
122- 0
211+
212+ fun isLightOn (context : Context , type : Int ): Boolean = mIsLightOn?.let { method ->
213+ if (method.parameterTypes.size == 2 ) {
214+ method.invoke(controller, context, type)
215+ } else {
216+ method.invoke(controller, type)
123217 }
124- }
218+ } as ? Boolean ? : false
219+
220+ // ── Write ─────────────────────────────────────────────────────────────────
221+ //
222+ // Two different axes — this is the source of both bugs:
223+ //
224+ // open/close → operates on the HARDWARE CHANNEL:
225+ // LIGHT_TYPE_CTM_ALL (4) for CTM mode (types 6 and 7 are logical params,
226+ // not hardware channels — opening
227+ // type 6 or 7 individually does nothing)
228+ // LIGHT_TYPE_CTM_WARM (2) / LIGHT_TYPE_CTM_COLD (3) for WARM_AND_COLD
229+ // LIGHT_TYPE_FL (1) for FL
230+ //
231+ // setLightValue → operates on the LOGICAL PARAMETER (type passed as-is):
232+ // type 6 = temperature, type 7 = brightness in CTM mode
233+ // type 2 = warm, type 3 = cold in WARM_AND_COLD mode
234+ // type 1 = brightness in FL mode
235+ //
236+ // Bug 1 — "Off mode": channel is powered down; setLightValue() alone is ignored.
237+ // Fix: call openFrontLight(channelType) before setLightValue().
238+ //
239+ // Bug 2 — "Custom mode warmth": setLight(LIGHT_TYPE_TEMP=6, ...) was calling
240+ // openFrontLight(6), but type 6 is not a hardware channel — only type 4 is.
241+ // Fix: map types 6 and 7 to channelType = LIGHT_TYPE_CTM_ALL (4) for open/close.
125242
126- fun getCold (context : Context ? ): Int {
127- return try {
128- getCoolWarmBrightness!! .invoke(flController!! , context, BRIGHTNESS_CONFIG_COLD_IDX ) as Int
129- } catch (e: Exception ) {
130- e.printStackTrace()
131- 0
243+ fun setLight (context : Context , type : Int , value : Int ): Boolean {
244+ // Map logical parameter type → physical channel type for open/close.
245+ // For CTM, type 4 (ALL) controls the master switch.
246+ // For WARM_AND_COLD, channels 2 and 3 are independent.
247+ val channelType = when (type) {
248+ LIGHT_TYPE_CTM_BR ,
249+ LIGHT_TYPE_TEMP -> LIGHT_TYPE_CTM_ALL // CTM: always open/close the whole unit
250+ else -> type // FL / WARM / COLD: direct channel
132251 }
133- }
134252
135- fun setWarm (value : Int , context : Context ? ) {
136- try {
137- setWarmBrightness!! .invoke(flController!! , context, value)
138- } catch (e: Exception ) {
139- e.printStackTrace()
253+ return if (value == 0 ) {
254+ // Only close the master switch if it's CTM brightness (7) or FL brightness (1).
255+ // Closing warmth channel (2 or 6) shouldn't turn off all lights.
256+ val shouldClose = when (type) {
257+ LIGHT_TYPE_FL ,
258+ LIGHT_TYPE_CTM_BR ,
259+ LIGHT_TYPE_CTM_COLD ,
260+ LIGHT_TYPE_CTM_WARM -> true
261+ else -> false
262+ }
263+
264+ if (shouldClose) {
265+ val ok = mCloseFrontLight?.invoke(controller, channelType) as ? Boolean ? : false
266+ Log .v(TAG , " closeFrontLight(channelType=$channelType ) → $ok " )
267+ ok
268+ } else {
269+ // For warmth, just set value to 0
270+ val ok = mSetLightValue?.invoke(controller, type, 0 ) as ? Boolean ? : false
271+ Log .v(TAG , " setLightValue(type=$type , value=0) → $ok " )
272+ ok
273+ }
274+ } else {
275+ if (! isLightOn(context, channelType)) {
276+ val opened = mOpenFrontLight?.invoke(controller, channelType) as ? Boolean ? : false
277+ Log .v(TAG , " openFrontLight(channelType=$channelType ) → $opened " )
278+ }
279+ val ok = mSetLightValue?.invoke(controller, type, value) as ? Boolean ? : false
280+ Log .v(TAG , " setLightValue(type=$type , value=$value ) → $ok " )
281+ ok
140282 }
141283 }
142284
143- fun setCold ( value : Int , context : Context ? ) {
144- try {
145- setColdBrightness !! .invoke(flController !! , context, value)
146- } catch (e : Exception ) {
147- e.printStackTrace()
148- }
285+ // ── Helpers ───────────────────────────────────────────────────────────────
286+
287+ private fun method ( name : String , vararg params : Class < * >): Method ? = try {
288+ controller?.getMethod(name, * params)
289+ } catch (e : Exception ) {
290+ Log .w( TAG , " Method ' $name ' not found: $e " ); null
149291 }
150292}
0 commit comments