1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.tv.twopanelsettings.slices 17 18 import android.content.Context 19 import android.content.ContextWrapper 20 import android.os.Bundle 21 import android.util.AttributeSet 22 import android.util.Log 23 import android.view.ContextThemeWrapper 24 import androidx.preference.Preference 25 import java.lang.reflect.Constructor 26 import java.lang.reflect.Array 27 import java.lang.reflect.Field 28 import java.lang.reflect.Method 29 import java.util.Locale 30 31 class NonSlicePreferenceBuilder private constructor(className: String) { 32 private val cls : Class<*> = Class.forName(className) 33 private val factory: Constructor<*> 34 private val setters: MutableMap<String, MutableList<Method>> = mutableMapOf() 35 <lambda>null36 init { 37 if (!Preference::class.java.isAssignableFrom(cls)) { 38 throw IllegalArgumentException("Not a preference") 39 } 40 41 factory = try { 42 cls.getConstructor(Context::class.java) 43 } catch (e: Exception) { 44 cls.getConstructor(Context::class.java, AttributeSet::class.java) 45 } 46 } 47 48 createnull49 fun create(context: Context, bundle: Bundle?) : Preference { 50 synchronized(this) { 51 return createInternal(context, bundle) 52 } 53 } 54 55 @Suppress("DEPRECATION") // Types can not be determined statically. createInternalnull56 private fun createInternal(context: Context, bundle: Bundle?): Preference { 57 val preference: Preference = 58 (if (factory.parameters.size == 1) 59 factory.newInstance(context) else factory.newInstance(context, null)) as Preference 60 bundle ?: return preference 61 62 properties@ for (property in bundle.keySet()) { 63 val value = bundle[property]!! 64 val setterList = setters[property] ?: mutableListOf() 65 for (setter in setterList) { 66 try { 67 setter.invoke(preference, value) 68 continue@properties; 69 } catch (_: Exception) { 70 } 71 } 72 73 val setterName = "set" + property.substring(0..<1).uppercase(Locale.US) + 74 property.substring(1) 75 val setter = findSetter(setterName, value::class.java) 76 if (setter != null) { 77 setter.invoke(preference, value) 78 setterList += setter 79 setters[property] = setterList 80 } else { 81 Log.e( 82 TAG, 83 "Can't find $setterName in ${cls.name} of type ${value::class.java.name}" 84 ); 85 } 86 } 87 88 return preference 89 } 90 findSetternull91 private fun findSetter(name: String, type: Class<*>) : Method? { 92 try { 93 return cls.getMethod(name, type) 94 } catch (_: Exception) {} 95 96 if (type.isArray) { 97 val componentType = type.componentType 98 val baseSuperClass = componentType.superclass 99 if (baseSuperClass != null) { 100 findSetter(name, Array.newInstance(baseSuperClass, 0)::class.java)?.let { return it } 101 } 102 103 for (baseInterface in componentType.interfaces) { 104 findSetter(name, Array.newInstance(baseInterface, 0)::class.java)?.let { return it } 105 } 106 107 return null 108 } 109 110 try { 111 val primitiveField = type.getField("TYPE") 112 val primitiveType = primitiveField.get(null) 113 if (primitiveType is Class<*>) { 114 findSetter(name, primitiveType)?.let { return it } 115 } 116 } catch(_: Exception) {} 117 118 val superclass = type.superclass 119 if (superclass != null) { 120 findSetter(name, superclass)?.let { return it } 121 } 122 123 for (iface in type.interfaces) { 124 findSetter(name, iface)?.let { return it } 125 } 126 127 return null 128 } 129 130 companion object { 131 private const val TAG = "NonSlicePreferenceBld" 132 133 private val builders: MutableMap<String, NonSlicePreferenceBuilder> = mutableMapOf() 134 forClassNamenull135 fun forClassName(className: String): NonSlicePreferenceBuilder { 136 synchronized(builders) { 137 var builder = builders[className] 138 if (builder == null) { 139 builder = NonSlicePreferenceBuilder(className) 140 builders[className] = builder 141 } 142 return builder 143 } 144 } 145 } 146 } 147