Fabian Kozynski | f10b6ab | 2019-12-27 09:31:04 -0500 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2020 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 | |
| 17 | package com.android.systemui.controls.ui |
| 18 | |
Matt Pietal | cd757c8 | 2020-04-08 10:20:48 -0400 | [diff] [blame] | 19 | import android.animation.Animator |
| 20 | import android.animation.AnimatorListenerAdapter |
| 21 | import android.animation.ObjectAnimator |
| 22 | import android.app.AlertDialog |
Matt Pietal | b7da66c | 2020-03-06 10:49:31 -0500 | [diff] [blame] | 23 | import android.app.Dialog |
Fabian Kozynski | f10b6ab | 2019-12-27 09:31:04 -0500 | [diff] [blame] | 24 | import android.content.ComponentName |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 25 | import android.content.Context |
Matt Pietal | cd757c8 | 2020-04-08 10:20:48 -0400 | [diff] [blame] | 26 | import android.content.DialogInterface |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 27 | import android.content.Intent |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 28 | import android.content.SharedPreferences |
Matt Pietal | 5f478c7 | 2020-04-01 15:53:54 -0400 | [diff] [blame] | 29 | import android.content.res.Configuration |
Matt Pietal | 37cc190 | 2020-02-06 10:25:56 -0500 | [diff] [blame] | 30 | import android.graphics.drawable.Drawable |
Matt Pietal | 9ef947a | 2020-03-04 08:22:03 -0500 | [diff] [blame] | 31 | import android.graphics.drawable.LayerDrawable |
Matt Pietal | cd757c8 | 2020-04-08 10:20:48 -0400 | [diff] [blame] | 32 | import android.os.Process |
Matt Pietal | dc78c84 | 2020-03-30 08:09:18 -0400 | [diff] [blame] | 33 | import android.provider.Settings |
Fabian Kozynski | f10b6ab | 2019-12-27 09:31:04 -0500 | [diff] [blame] | 34 | import android.service.controls.Control |
Matt Pietal | b7da66c | 2020-03-06 10:49:31 -0500 | [diff] [blame] | 35 | import android.service.controls.actions.ControlAction |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 36 | import android.util.Log |
Fabian Kozynski | 8765d35 | 2020-04-06 21:16:02 -0400 | [diff] [blame] | 37 | import android.util.TypedValue |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 38 | import android.view.ContextThemeWrapper |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 39 | import android.view.LayoutInflater |
| 40 | import android.view.View |
Matt Pietal | 5ebb7fe | 2020-03-20 12:58:54 -0400 | [diff] [blame] | 41 | import android.view.View.MeasureSpec |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 42 | import android.view.ViewGroup |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 43 | import android.view.WindowManager |
Fabian Kozynski | 8765d35 | 2020-04-06 21:16:02 -0400 | [diff] [blame] | 44 | import android.view.animation.AccelerateInterpolator |
| 45 | import android.view.animation.DecelerateInterpolator |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 46 | import android.widget.AdapterView |
| 47 | import android.widget.ArrayAdapter |
Matt Pietal | 37cc190 | 2020-02-06 10:25:56 -0500 | [diff] [blame] | 48 | import android.widget.ImageView |
Matt Pietal | e0661b6 | 2020-01-29 14:35:31 -0500 | [diff] [blame] | 49 | import android.widget.LinearLayout |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 50 | import android.widget.ListPopupWindow |
Matt Pietal | e0661b6 | 2020-01-29 14:35:31 -0500 | [diff] [blame] | 51 | import android.widget.Space |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 52 | import android.widget.TextView |
Fabian Kozynski | 8765d35 | 2020-04-06 21:16:02 -0400 | [diff] [blame] | 53 | import com.android.systemui.R |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 54 | import com.android.systemui.controls.ControlsServiceInfo |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 55 | import com.android.systemui.controls.controller.ControlInfo |
Matt Pietal | 313f37d | 2020-02-24 11:27:22 -0500 | [diff] [blame] | 56 | import com.android.systemui.controls.controller.ControlsController |
| 57 | import com.android.systemui.controls.controller.StructureInfo |
Fabian Kozynski | 8765d35 | 2020-04-06 21:16:02 -0400 | [diff] [blame] | 58 | import com.android.systemui.controls.management.ControlsEditingActivity |
Matt Pietal | d2ea531 | 2020-04-06 11:27:02 -0400 | [diff] [blame] | 59 | import com.android.systemui.controls.management.ControlsFavoritingActivity |
Matt Pietal | 37cc190 | 2020-02-06 10:25:56 -0500 | [diff] [blame] | 60 | import com.android.systemui.controls.management.ControlsListingController |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 61 | import com.android.systemui.controls.management.ControlsProviderSelectorActivity |
Matt Pietal | 37cc190 | 2020-02-06 10:25:56 -0500 | [diff] [blame] | 62 | import com.android.systemui.dagger.qualifiers.Background |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 63 | import com.android.systemui.dagger.qualifiers.Main |
Matt Pietal | 1aac43b | 2020-02-04 15:43:31 -0500 | [diff] [blame] | 64 | import com.android.systemui.util.concurrency.DelayableExecutor |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 65 | import dagger.Lazy |
Matt Pietal | 37cc190 | 2020-02-06 10:25:56 -0500 | [diff] [blame] | 66 | import java.text.Collator |
Matt Pietal | 6126644 | 2020-03-17 12:53:44 -0400 | [diff] [blame] | 67 | import java.util.function.Consumer |
Fabian Kozynski | f10b6ab | 2019-12-27 09:31:04 -0500 | [diff] [blame] | 68 | import javax.inject.Inject |
| 69 | import javax.inject.Singleton |
| 70 | |
Matt Pietal | 1aac43b | 2020-02-04 15:43:31 -0500 | [diff] [blame] | 71 | private data class ControlKey(val componentName: ComponentName, val controlId: String) |
| 72 | |
Fabian Kozynski | f10b6ab | 2019-12-27 09:31:04 -0500 | [diff] [blame] | 73 | @Singleton |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 74 | class ControlsUiControllerImpl @Inject constructor ( |
| 75 | val controlsController: Lazy<ControlsController>, |
| 76 | val context: Context, |
Matt Pietal | 37cc190 | 2020-02-06 10:25:56 -0500 | [diff] [blame] | 77 | @Main val uiExecutor: DelayableExecutor, |
| 78 | @Background val bgExecutor: DelayableExecutor, |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 79 | val controlsListingController: Lazy<ControlsListingController>, |
| 80 | @Main val sharedPreferences: SharedPreferences |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 81 | ) : ControlsUiController { |
| 82 | |
Matt Pietal | 313f37d | 2020-02-24 11:27:22 -0500 | [diff] [blame] | 83 | companion object { |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 84 | private const val PREF_COMPONENT = "controls_component" |
| 85 | private const val PREF_STRUCTURE = "controls_structure" |
| 86 | |
Matt Pietal | dc78c84 | 2020-03-30 08:09:18 -0400 | [diff] [blame] | 87 | private const val USE_PANELS = "systemui.controls_use_panel" |
Matt Pietal | cd757c8 | 2020-04-08 10:20:48 -0400 | [diff] [blame] | 88 | private const val FADE_IN_MILLIS = 225L |
| 89 | |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 90 | private val EMPTY_COMPONENT = ComponentName("", "") |
Matt Pietal | 313f37d | 2020-02-24 11:27:22 -0500 | [diff] [blame] | 91 | private val EMPTY_STRUCTURE = StructureInfo( |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 92 | EMPTY_COMPONENT, |
Matt Pietal | 313f37d | 2020-02-24 11:27:22 -0500 | [diff] [blame] | 93 | "", |
| 94 | mutableListOf<ControlInfo>() |
| 95 | ) |
| 96 | } |
| 97 | |
| 98 | private var selectedStructure: StructureInfo = EMPTY_STRUCTURE |
| 99 | private lateinit var allStructures: List<StructureInfo> |
Matt Pietal | 1aac43b | 2020-02-04 15:43:31 -0500 | [diff] [blame] | 100 | private val controlsById = mutableMapOf<ControlKey, ControlWithState>() |
| 101 | private val controlViewsById = mutableMapOf<ControlKey, ControlViewHolder>() |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 102 | private lateinit var parent: ViewGroup |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 103 | private lateinit var lastItems: List<SelectionItem> |
| 104 | private var popup: ListPopupWindow? = null |
Matt Pietal | b7da66c | 2020-03-06 10:49:31 -0500 | [diff] [blame] | 105 | private var activeDialog: Dialog? = null |
Matt Pietal | 6126644 | 2020-03-17 12:53:44 -0400 | [diff] [blame] | 106 | private var hidden = true |
Matt Pietal | 9ef947a | 2020-03-04 08:22:03 -0500 | [diff] [blame] | 107 | |
Fabian Kozynski | 8b54045 | 2020-02-04 15:16:30 -0500 | [diff] [blame] | 108 | override val available: Boolean |
| 109 | get() = controlsController.get().available |
| 110 | |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 111 | private lateinit var listingCallback: ControlsListingController.ControlsListingCallback |
Matt Pietal | 37cc190 | 2020-02-06 10:25:56 -0500 | [diff] [blame] | 112 | |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 113 | private fun createCallback( |
| 114 | onResult: (List<SelectionItem>) -> Unit |
| 115 | ): ControlsListingController.ControlsListingCallback { |
| 116 | return object : ControlsListingController.ControlsListingCallback { |
| 117 | override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) { |
| 118 | bgExecutor.execute { |
| 119 | val collator = Collator.getInstance(context.resources.configuration.locales[0]) |
| 120 | val localeComparator = compareBy<ControlsServiceInfo, CharSequence>(collator) { |
| 121 | it.loadLabel() |
| 122 | } |
| 123 | |
| 124 | val mList = serviceInfos.toMutableList() |
| 125 | mList.sortWith(localeComparator) |
| 126 | lastItems = mList.map { |
| 127 | SelectionItem(it.loadLabel(), "", it.loadIcon(), it.componentName) |
| 128 | } |
| 129 | uiExecutor.execute { |
| 130 | onResult(lastItems) |
| 131 | } |
| 132 | } |
Matt Pietal | 37cc190 | 2020-02-06 10:25:56 -0500 | [diff] [blame] | 133 | } |
| 134 | } |
| 135 | } |
| 136 | |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 137 | override fun show(parent: ViewGroup) { |
Matt Pietal | 307b1ef | 2020-02-10 07:27:01 -0500 | [diff] [blame] | 138 | Log.d(ControlsUiController.TAG, "show()") |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 139 | this.parent = parent |
Matt Pietal | 6126644 | 2020-03-17 12:53:44 -0400 | [diff] [blame] | 140 | hidden = false |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 141 | |
Matt Pietal | 313f37d | 2020-02-24 11:27:22 -0500 | [diff] [blame] | 142 | allStructures = controlsController.get().getFavorites() |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 143 | selectedStructure = loadPreference(allStructures) |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 144 | |
Matt Pietal | 6126644 | 2020-03-17 12:53:44 -0400 | [diff] [blame] | 145 | val cb = Consumer<Boolean> { _ -> reload(parent) } |
| 146 | if (controlsController.get().addSeedingFavoritesCallback(cb)) { |
| 147 | listingCallback = createCallback(::showSeedingView) |
| 148 | } else if (selectedStructure.controls.isEmpty() && allStructures.size <= 1) { |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 149 | // only show initial view if there are really no favorites across any structure |
| 150 | listingCallback = createCallback(::showInitialSetupView) |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 151 | } else { |
Matt Pietal | 313f37d | 2020-02-24 11:27:22 -0500 | [diff] [blame] | 152 | selectedStructure.controls.map { |
| 153 | ControlWithState(selectedStructure.componentName, it, null) |
| 154 | }.associateByTo(controlsById) { |
| 155 | ControlKey(selectedStructure.componentName, it.ci.controlId) |
| 156 | } |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 157 | listingCallback = createCallback(::showControlsView) |
Matt Pietal | fec10fa | 2020-03-09 14:42:46 -0400 | [diff] [blame] | 158 | controlsController.get().subscribeToFavorites(selectedStructure) |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 159 | } |
| 160 | |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 161 | controlsListingController.get().addCallback(listingCallback) |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 162 | } |
| 163 | |
Matt Pietal | 6126644 | 2020-03-17 12:53:44 -0400 | [diff] [blame] | 164 | private fun reload(parent: ViewGroup) { |
| 165 | if (hidden) return |
Matt Pietal | cd757c8 | 2020-04-08 10:20:48 -0400 | [diff] [blame] | 166 | |
| 167 | val fadeAnim = ObjectAnimator.ofFloat(parent, "alpha", 1.0f, 0.0f) |
| 168 | fadeAnim.setInterpolator(AccelerateInterpolator(1.0f)) |
| 169 | fadeAnim.setDuration(FADE_IN_MILLIS) |
| 170 | fadeAnim.addListener(object : AnimatorListenerAdapter() { |
| 171 | override fun onAnimationEnd(animation: Animator) { |
| 172 | show(parent) |
| 173 | val showAnim = ObjectAnimator.ofFloat(parent, "alpha", 0.0f, 1.0f) |
| 174 | showAnim.setInterpolator(DecelerateInterpolator(1.0f)) |
| 175 | showAnim.setDuration(FADE_IN_MILLIS) |
| 176 | showAnim.start() |
| 177 | } |
| 178 | }) |
| 179 | fadeAnim.start() |
Matt Pietal | 6126644 | 2020-03-17 12:53:44 -0400 | [diff] [blame] | 180 | } |
| 181 | |
| 182 | private fun showSeedingView(items: List<SelectionItem>) { |
| 183 | parent.removeAllViews() |
| 184 | |
| 185 | val inflater = LayoutInflater.from(context) |
| 186 | inflater.inflate(R.layout.controls_no_favorites, parent, true) |
| 187 | val subtitle = parent.requireViewById<TextView>(R.id.controls_subtitle) |
Matt Pietal | 9e3d40d | 2020-03-24 14:38:39 -0400 | [diff] [blame] | 188 | subtitle.setText(context.resources.getString(R.string.controls_seeding_in_progress)) |
Matt Pietal | 6126644 | 2020-03-17 12:53:44 -0400 | [diff] [blame] | 189 | } |
| 190 | |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 191 | private fun showInitialSetupView(items: List<SelectionItem>) { |
| 192 | parent.removeAllViews() |
| 193 | |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 194 | val inflater = LayoutInflater.from(context) |
| 195 | inflater.inflate(R.layout.controls_no_favorites, parent, true) |
| 196 | |
Matt Pietal | 37cc190 | 2020-02-06 10:25:56 -0500 | [diff] [blame] | 197 | val viewGroup = parent.requireViewById(R.id.controls_no_favorites_group) as ViewGroup |
Matt Pietal | d2ea531 | 2020-04-06 11:27:02 -0400 | [diff] [blame] | 198 | viewGroup.setOnClickListener { v: View -> startProviderSelectorActivity(v.context) } |
Matt Pietal | 37cc190 | 2020-02-06 10:25:56 -0500 | [diff] [blame] | 199 | |
Matt Pietal | 9e3d40d | 2020-03-24 14:38:39 -0400 | [diff] [blame] | 200 | val subtitle = parent.requireViewById<TextView>(R.id.controls_subtitle) |
| 201 | subtitle.setText(context.resources.getString(R.string.quick_controls_subtitle)) |
| 202 | |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 203 | val iconRowGroup = parent.requireViewById(R.id.controls_icon_row) as ViewGroup |
| 204 | items.forEach { |
| 205 | val imageView = inflater.inflate(R.layout.controls_icon, viewGroup, false) as ImageView |
| 206 | imageView.setContentDescription(it.getTitle()) |
| 207 | imageView.setImageDrawable(it.icon) |
| 208 | iconRowGroup.addView(imageView) |
Matt Pietal | 37cc190 | 2020-02-06 10:25:56 -0500 | [diff] [blame] | 209 | } |
Matt Pietal | e0661b6 | 2020-01-29 14:35:31 -0500 | [diff] [blame] | 210 | } |
| 211 | |
Lucas Dupin | d60b332 | 2020-04-15 18:06:47 -0700 | [diff] [blame] | 212 | override fun onFocusChanged(focusedControl: ControlWithState?) { |
| 213 | controlViewsById.forEach { key: ControlKey, viewHolder: ControlViewHolder -> |
| 214 | val state = controlsById.get(key) ?: return@forEach |
| 215 | val shouldBeDimmed = focusedControl != null && state != focusedControl |
| 216 | if (viewHolder.dimmed == shouldBeDimmed) { |
| 217 | return@forEach |
| 218 | } |
| 219 | |
| 220 | uiExecutor.execute { |
| 221 | viewHolder.dimmed = shouldBeDimmed |
| 222 | } |
| 223 | } |
| 224 | } |
| 225 | |
Matt Pietal | d2ea531 | 2020-04-06 11:27:02 -0400 | [diff] [blame] | 226 | private fun startFavoritingActivity(context: Context, si: StructureInfo) { |
Fabian Kozynski | 8765d35 | 2020-04-06 21:16:02 -0400 | [diff] [blame] | 227 | startTargetedActivity(context, si, ControlsFavoritingActivity::class.java) |
| 228 | } |
| 229 | |
| 230 | private fun startEditingActivity(context: Context, si: StructureInfo) { |
| 231 | startTargetedActivity(context, si, ControlsEditingActivity::class.java) |
| 232 | } |
| 233 | |
| 234 | private fun startTargetedActivity(context: Context, si: StructureInfo, klazz: Class<*>) { |
| 235 | val i = Intent(context, klazz).apply { |
Matt Pietal | d2ea531 | 2020-04-06 11:27:02 -0400 | [diff] [blame] | 236 | addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 237 | } |
Fabian Kozynski | 8765d35 | 2020-04-06 21:16:02 -0400 | [diff] [blame] | 238 | putIntentExtras(i, si) |
Matt Pietal | d2ea531 | 2020-04-06 11:27:02 -0400 | [diff] [blame] | 239 | startActivity(context, i) |
| 240 | } |
| 241 | |
Fabian Kozynski | 8765d35 | 2020-04-06 21:16:02 -0400 | [diff] [blame] | 242 | private fun putIntentExtras(intent: Intent, si: StructureInfo) { |
| 243 | intent.apply { |
| 244 | putExtra(ControlsFavoritingActivity.EXTRA_APP, |
| 245 | controlsListingController.get().getAppLabel(si.componentName)) |
| 246 | putExtra(ControlsFavoritingActivity.EXTRA_STRUCTURE, si.structure) |
| 247 | putExtra(Intent.EXTRA_COMPONENT_NAME, si.componentName) |
| 248 | } |
| 249 | } |
| 250 | |
Matt Pietal | d2ea531 | 2020-04-06 11:27:02 -0400 | [diff] [blame] | 251 | private fun startProviderSelectorActivity(context: Context) { |
| 252 | val i = Intent(context, ControlsProviderSelectorActivity::class.java).apply { |
| 253 | addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) |
| 254 | } |
| 255 | startActivity(context, i) |
| 256 | } |
| 257 | |
| 258 | private fun startActivity(context: Context, intent: Intent) { |
| 259 | val closeDialog = Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS) |
| 260 | context.sendBroadcast(closeDialog) |
| 261 | context.startActivity(intent) |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 262 | } |
| 263 | |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 264 | private fun showControlsView(items: List<SelectionItem>) { |
| 265 | parent.removeAllViews() |
| 266 | controlViewsById.clear() |
| 267 | |
Matt Pietal | 53a8bbd | 2020-03-05 16:10:34 -0500 | [diff] [blame] | 268 | createListView() |
| 269 | createDropDown(items) |
Matt Pietal | 5ebb7fe | 2020-03-20 12:58:54 -0400 | [diff] [blame] | 270 | createMenu() |
| 271 | } |
| 272 | |
| 273 | private fun createPopup(): ListPopupWindow { |
| 274 | return ListPopupWindow( |
| 275 | ContextThemeWrapper(context, R.style.Control_ListPopupWindow)).apply { |
| 276 | setWindowLayoutType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY) |
| 277 | setModal(true) |
| 278 | } |
| 279 | } |
| 280 | |
| 281 | private fun createMenu() { |
| 282 | val items = arrayOf( |
Matt Pietal | cd757c8 | 2020-04-08 10:20:48 -0400 | [diff] [blame] | 283 | context.resources.getString(R.string.controls_menu_add), |
Fabian Kozynski | 8765d35 | 2020-04-06 21:16:02 -0400 | [diff] [blame] | 284 | context.resources.getString(R.string.controls_menu_edit), |
Matt Pietal | cd757c8 | 2020-04-08 10:20:48 -0400 | [diff] [blame] | 285 | "Reset" |
Matt Pietal | 5ebb7fe | 2020-03-20 12:58:54 -0400 | [diff] [blame] | 286 | ) |
| 287 | var adapter = ArrayAdapter<String>(context, R.layout.controls_more_item, items) |
| 288 | |
| 289 | val anchor = parent.requireViewById<ImageView>(R.id.controls_more) |
| 290 | anchor.setOnClickListener(object : View.OnClickListener { |
| 291 | override fun onClick(v: View) { |
| 292 | popup = createPopup().apply { |
| 293 | setAnchorView(anchor) |
| 294 | setAdapter(adapter) |
| 295 | setOnItemClickListener(object : AdapterView.OnItemClickListener { |
| 296 | override fun onItemClick( |
| 297 | parent: AdapterView<*>, |
| 298 | view: View, |
| 299 | pos: Int, |
| 300 | id: Long |
| 301 | ) { |
| 302 | when (pos) { |
| 303 | // 0: Add Control |
Matt Pietal | d2ea531 | 2020-04-06 11:27:02 -0400 | [diff] [blame] | 304 | 0 -> startFavoritingActivity(view.context, selectedStructure) |
Fabian Kozynski | 8765d35 | 2020-04-06 21:16:02 -0400 | [diff] [blame] | 305 | // 1: Edit controls |
| 306 | 1 -> startEditingActivity(view.context, selectedStructure) |
| 307 | // 2: TEMPORARY for reset controls |
| 308 | 2 -> showResetConfirmation() |
Matt Pietal | 5ebb7fe | 2020-03-20 12:58:54 -0400 | [diff] [blame] | 309 | else -> Log.w(ControlsUiController.TAG, |
| 310 | "Unsupported index ($pos) on 'more' menu selection") |
| 311 | } |
| 312 | dismiss() |
| 313 | } |
| 314 | }) |
| 315 | // need to call show() first in order to construct the listView |
| 316 | show() |
| 317 | var width = 0 |
| 318 | getListView()?.apply { |
| 319 | // width should be between [.5, .9] of screen |
| 320 | val parentWidth = this@ControlsUiControllerImpl.parent.getWidth() |
| 321 | val widthSpec = MeasureSpec.makeMeasureSpec( |
| 322 | (parentWidth * 0.9).toInt(), MeasureSpec.AT_MOST) |
| 323 | val child = adapter.getView(0, null, this) |
| 324 | child.measure(widthSpec, MeasureSpec.UNSPECIFIED) |
| 325 | width = Math.max(child.getMeasuredWidth(), (parentWidth * 0.5).toInt()) |
| 326 | } |
| 327 | setWidth(width) |
| 328 | setHorizontalOffset(-width + anchor.getWidth()) |
| 329 | show() |
| 330 | } |
| 331 | } |
| 332 | }) |
Matt Pietal | 53a8bbd | 2020-03-05 16:10:34 -0500 | [diff] [blame] | 333 | } |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 334 | |
Matt Pietal | cd757c8 | 2020-04-08 10:20:48 -0400 | [diff] [blame] | 335 | private fun showResetConfirmation() { |
| 336 | val builder = AlertDialog.Builder( |
| 337 | context, |
| 338 | android.R.style.Theme_DeviceDefault_Dialog_Alert |
| 339 | ).apply { |
| 340 | setMessage("For testing purposes: Would you like to " + |
| 341 | "reset your favorited device controls?") |
| 342 | setPositiveButton( |
| 343 | android.R.string.ok, |
| 344 | DialogInterface.OnClickListener { dialog, _ -> |
| 345 | val userHandle = Process.myUserHandle() |
| 346 | val userContext = context.createContextAsUser(userHandle, 0) |
| 347 | val prefs = userContext.getSharedPreferences( |
| 348 | "controls_prefs", Context.MODE_PRIVATE) |
| 349 | prefs.edit().putBoolean("ControlsSeedingCompleted", false).apply() |
| 350 | controlsController.get().resetFavorites() |
| 351 | dialog.dismiss() |
| 352 | context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) |
| 353 | }) |
| 354 | setNegativeButton( |
| 355 | android.R.string.cancel, |
| 356 | DialogInterface.OnClickListener { |
| 357 | dialog, _ -> dialog.cancel() |
| 358 | } |
| 359 | ) |
| 360 | } |
| 361 | builder.create().apply { |
| 362 | getWindow().apply { |
| 363 | setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY) |
| 364 | } |
| 365 | }.show() |
| 366 | } |
| 367 | |
Matt Pietal | 53a8bbd | 2020-03-05 16:10:34 -0500 | [diff] [blame] | 368 | private fun createDropDown(items: List<SelectionItem>) { |
| 369 | items.forEach { |
| 370 | RenderInfo.registerComponentIcon(it.componentName, it.icon) |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 371 | } |
Matt Pietal | e0661b6 | 2020-01-29 14:35:31 -0500 | [diff] [blame] | 372 | |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 373 | val itemsByComponent = items.associateBy { it.componentName } |
Matt Pietal | 53a8bbd | 2020-03-05 16:10:34 -0500 | [diff] [blame] | 374 | val itemsWithStructure = allStructures.mapNotNull { |
| 375 | itemsByComponent.get(it.componentName)?.copy(structure = it.structure) |
| 376 | } |
| 377 | val selectionItem = findSelectionItem(selectedStructure, itemsWithStructure) ?: items[0] |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 378 | |
Matt Pietal | 53a8bbd | 2020-03-05 16:10:34 -0500 | [diff] [blame] | 379 | var adapter = ItemAdapter(context, R.layout.controls_spinner_item).apply { |
Matt Pietal | 5ebb7fe | 2020-03-20 12:58:54 -0400 | [diff] [blame] | 380 | addAll(itemsWithStructure) |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 381 | } |
| 382 | |
| 383 | /* |
| 384 | * Default spinner widget does not work with the window type required |
| 385 | * for this dialog. Use a textView with the ListPopupWindow to achieve |
| 386 | * a similar effect |
| 387 | */ |
Matt Pietal | 5ebb7fe | 2020-03-20 12:58:54 -0400 | [diff] [blame] | 388 | val spinner = parent.requireViewById<TextView>(R.id.app_or_structure_spinner).apply { |
Matt Pietal | 53a8bbd | 2020-03-05 16:10:34 -0500 | [diff] [blame] | 389 | setText(selectionItem.getTitle()) |
Matt Pietal | 9ef947a | 2020-03-04 08:22:03 -0500 | [diff] [blame] | 390 | // override the default color on the dropdown drawable |
| 391 | (getBackground() as LayerDrawable).getDrawable(1) |
| 392 | .setTint(context.resources.getColor(R.color.control_spinner_dropdown, null)) |
Matt Pietal | aaccdbe | 2020-03-04 08:22:03 -0500 | [diff] [blame] | 393 | } |
| 394 | parent.requireViewById<ImageView>(R.id.app_icon).apply { |
Matt Pietal | 53a8bbd | 2020-03-05 16:10:34 -0500 | [diff] [blame] | 395 | setImageDrawable(selectionItem.icon) |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 396 | } |
Matt Pietal | 5ebb7fe | 2020-03-20 12:58:54 -0400 | [diff] [blame] | 397 | |
| 398 | if (itemsWithStructure.size == 1) { |
| 399 | spinner.setBackground(null) |
| 400 | return |
| 401 | } |
| 402 | |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 403 | val anchor = parent.requireViewById<ViewGroup>(R.id.controls_header) |
| 404 | anchor.setOnClickListener(object : View.OnClickListener { |
| 405 | override fun onClick(v: View) { |
Matt Pietal | 5ebb7fe | 2020-03-20 12:58:54 -0400 | [diff] [blame] | 406 | popup = createPopup().apply { |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 407 | setAnchorView(anchor) |
| 408 | setAdapter(adapter) |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 409 | setOnItemClickListener(object : AdapterView.OnItemClickListener { |
| 410 | override fun onItemClick( |
| 411 | parent: AdapterView<*>, |
| 412 | view: View, |
| 413 | pos: Int, |
| 414 | id: Long |
| 415 | ) { |
| 416 | val listItem = parent.getItemAtPosition(pos) as SelectionItem |
| 417 | this@ControlsUiControllerImpl.switchAppOrStructure(listItem) |
| 418 | dismiss() |
| 419 | } |
| 420 | }) |
| 421 | // need to call show() first in order to construct the listView |
| 422 | show() |
| 423 | getListView()?.apply { |
| 424 | setDividerHeight( |
| 425 | context.resources.getDimensionPixelSize(R.dimen.control_list_divider)) |
| 426 | setDivider( |
| 427 | context.resources.getDrawable(R.drawable.controls_list_divider)) |
| 428 | } |
| 429 | show() |
| 430 | } |
| 431 | } |
| 432 | }) |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 433 | } |
| 434 | |
Matt Pietal | 53a8bbd | 2020-03-05 16:10:34 -0500 | [diff] [blame] | 435 | private fun createListView() { |
| 436 | val inflater = LayoutInflater.from(context) |
| 437 | inflater.inflate(R.layout.controls_with_favorites, parent, true) |
| 438 | |
Matt Pietal | 5f478c7 | 2020-04-01 15:53:54 -0400 | [diff] [blame] | 439 | val maxColumns = findMaxColumns() |
| 440 | |
Matt Pietal | dc78c84 | 2020-03-30 08:09:18 -0400 | [diff] [blame] | 441 | // use flag only temporarily for testing |
| 442 | val usePanels = Settings.Secure.getInt(context.contentResolver, USE_PANELS, 0) == 1 |
| 443 | |
Matt Pietal | 53a8bbd | 2020-03-05 16:10:34 -0500 | [diff] [blame] | 444 | val listView = parent.requireViewById(R.id.global_actions_controls_list) as ViewGroup |
| 445 | var lastRow: ViewGroup = createRow(inflater, listView) |
| 446 | selectedStructure.controls.forEach { |
Matt Pietal | 5f478c7 | 2020-04-01 15:53:54 -0400 | [diff] [blame] | 447 | if (lastRow.getChildCount() == maxColumns) { |
Matt Pietal | 53a8bbd | 2020-03-05 16:10:34 -0500 | [diff] [blame] | 448 | lastRow = createRow(inflater, listView) |
| 449 | } |
| 450 | val baseLayout = inflater.inflate( |
| 451 | R.layout.controls_base_item, lastRow, false) as ViewGroup |
| 452 | lastRow.addView(baseLayout) |
| 453 | val cvh = ControlViewHolder( |
| 454 | baseLayout, |
| 455 | controlsController.get(), |
| 456 | uiExecutor, |
Matt Pietal | dc78c84 | 2020-03-30 08:09:18 -0400 | [diff] [blame] | 457 | bgExecutor, |
| 458 | usePanels |
Matt Pietal | 53a8bbd | 2020-03-05 16:10:34 -0500 | [diff] [blame] | 459 | ) |
| 460 | val key = ControlKey(selectedStructure.componentName, it.controlId) |
| 461 | cvh.bindData(controlsById.getValue(key)) |
| 462 | controlViewsById.put(key, cvh) |
| 463 | } |
| 464 | |
Matt Pietal | 5f478c7 | 2020-04-01 15:53:54 -0400 | [diff] [blame] | 465 | // add spacers if necessary to keep control size consistent |
Lucas Dupin | 1ffaf8c | 2020-04-09 11:13:07 -0700 | [diff] [blame] | 466 | val mod = selectedStructure.controls.size % maxColumns |
| 467 | var spacersToAdd = if (mod == 0) 0 else maxColumns - mod |
Matt Pietal | 5f478c7 | 2020-04-01 15:53:54 -0400 | [diff] [blame] | 468 | while (spacersToAdd > 0) { |
Matt Pietal | 53a8bbd | 2020-03-05 16:10:34 -0500 | [diff] [blame] | 469 | lastRow.addView(Space(context), LinearLayout.LayoutParams(0, 0, 1f)) |
Matt Pietal | 5f478c7 | 2020-04-01 15:53:54 -0400 | [diff] [blame] | 470 | spacersToAdd-- |
Matt Pietal | 53a8bbd | 2020-03-05 16:10:34 -0500 | [diff] [blame] | 471 | } |
| 472 | } |
| 473 | |
Matt Pietal | 5f478c7 | 2020-04-01 15:53:54 -0400 | [diff] [blame] | 474 | /** |
| 475 | * For low-dp width screens that also employ an increased font scale, adjust the |
| 476 | * number of columns. This helps prevent text truncation on these devices. |
| 477 | */ |
| 478 | private fun findMaxColumns(): Int { |
| 479 | val res = context.resources |
| 480 | var maxColumns = res.getInteger(R.integer.controls_max_columns) |
| 481 | val maxColumnsAdjustWidth = |
| 482 | res.getInteger(R.integer.controls_max_columns_adjust_below_width_dp) |
| 483 | |
| 484 | val outValue = TypedValue() |
| 485 | res.getValue(R.dimen.controls_max_columns_adjust_above_font_scale, outValue, true) |
| 486 | val maxColumnsAdjustFontScale = outValue.getFloat() |
| 487 | |
| 488 | val config = res.configuration |
| 489 | val isPortrait = config.orientation == Configuration.ORIENTATION_PORTRAIT |
| 490 | if (isPortrait && |
| 491 | config.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED && |
| 492 | config.screenWidthDp <= maxColumnsAdjustWidth && |
| 493 | config.fontScale >= maxColumnsAdjustFontScale) { |
| 494 | maxColumns-- |
| 495 | } |
| 496 | |
| 497 | return maxColumns |
| 498 | } |
| 499 | |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 500 | private fun loadPreference(structures: List<StructureInfo>): StructureInfo { |
| 501 | if (structures.isEmpty()) return EMPTY_STRUCTURE |
| 502 | |
| 503 | val component = sharedPreferences.getString(PREF_COMPONENT, null)?.let { |
| 504 | ComponentName.unflattenFromString(it) |
| 505 | } ?: EMPTY_COMPONENT |
| 506 | val structure = sharedPreferences.getString(PREF_STRUCTURE, "") |
| 507 | |
| 508 | return structures.firstOrNull { |
| 509 | component == it.componentName && structure == it.structure |
| 510 | } ?: structures.get(0) |
| 511 | } |
| 512 | |
| 513 | private fun updatePreferences(si: StructureInfo) { |
| 514 | sharedPreferences.edit() |
| 515 | .putString(PREF_COMPONENT, si.componentName.flattenToString()) |
| 516 | .putString(PREF_STRUCTURE, si.structure.toString()) |
| 517 | .commit() |
| 518 | } |
| 519 | |
| 520 | private fun switchAppOrStructure(item: SelectionItem) { |
Matt Pietal | 5ebb7fe | 2020-03-20 12:58:54 -0400 | [diff] [blame] | 521 | val newSelection = allStructures.first { |
| 522 | it.structure == item.structure && it.componentName == item.componentName |
| 523 | } |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 524 | |
Matt Pietal | 5ebb7fe | 2020-03-20 12:58:54 -0400 | [diff] [blame] | 525 | if (newSelection != selectedStructure) { |
| 526 | selectedStructure = newSelection |
| 527 | updatePreferences(selectedStructure) |
| 528 | controlsListingController.get().removeCallback(listingCallback) |
| 529 | reload(parent) |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 530 | } |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 531 | } |
| 532 | |
| 533 | override fun hide() { |
Matt Pietal | 307b1ef | 2020-02-10 07:27:01 -0500 | [diff] [blame] | 534 | Log.d(ControlsUiController.TAG, "hide()") |
Matt Pietal | 6126644 | 2020-03-17 12:53:44 -0400 | [diff] [blame] | 535 | hidden = true |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 536 | popup?.dismiss() |
Matt Pietal | b7da66c | 2020-03-06 10:49:31 -0500 | [diff] [blame] | 537 | activeDialog?.dismiss() |
Matt Pietal | dc78c84 | 2020-03-30 08:09:18 -0400 | [diff] [blame] | 538 | ControlActionCoordinator.closeDialog() |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 539 | |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 540 | controlsController.get().unsubscribe() |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 541 | |
| 542 | parent.removeAllViews() |
| 543 | controlsById.clear() |
| 544 | controlViewsById.clear() |
Matt Pietal | 37cc190 | 2020-02-06 10:25:56 -0500 | [diff] [blame] | 545 | controlsListingController.get().removeCallback(listingCallback) |
Matt Pietal | 53a8bbd | 2020-03-05 16:10:34 -0500 | [diff] [blame] | 546 | |
| 547 | RenderInfo.clearCache() |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 548 | } |
Fabian Kozynski | f10b6ab | 2019-12-27 09:31:04 -0500 | [diff] [blame] | 549 | |
| 550 | override fun onRefreshState(componentName: ComponentName, controls: List<Control>) { |
Matt Pietal | 307b1ef | 2020-02-10 07:27:01 -0500 | [diff] [blame] | 551 | Log.d(ControlsUiController.TAG, "onRefreshState()") |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 552 | controls.forEach { c -> |
Matt Pietal | 1aac43b | 2020-02-04 15:43:31 -0500 | [diff] [blame] | 553 | controlsById.get(ControlKey(componentName, c.getControlId()))?.let { |
Matt Pietal | 307b1ef | 2020-02-10 07:27:01 -0500 | [diff] [blame] | 554 | Log.d(ControlsUiController.TAG, "onRefreshState() for id: " + c.getControlId()) |
Matt Pietal | 313f37d | 2020-02-24 11:27:22 -0500 | [diff] [blame] | 555 | val cws = ControlWithState(componentName, it.ci, c) |
Matt Pietal | 1aac43b | 2020-02-04 15:43:31 -0500 | [diff] [blame] | 556 | val key = ControlKey(componentName, c.getControlId()) |
| 557 | controlsById.put(key, cws) |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 558 | |
| 559 | uiExecutor.execute { |
Matt Pietal | 1aac43b | 2020-02-04 15:43:31 -0500 | [diff] [blame] | 560 | controlViewsById.get(key)?.bindData(cws) |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 561 | } |
| 562 | } |
| 563 | } |
Fabian Kozynski | f10b6ab | 2019-12-27 09:31:04 -0500 | [diff] [blame] | 564 | } |
| 565 | |
| 566 | override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) { |
Matt Pietal | 1aac43b | 2020-02-04 15:43:31 -0500 | [diff] [blame] | 567 | val key = ControlKey(componentName, controlId) |
| 568 | uiExecutor.execute { |
Matt Pietal | b7da66c | 2020-03-06 10:49:31 -0500 | [diff] [blame] | 569 | controlViewsById.get(key)?.let { cvh -> |
| 570 | when (response) { |
| 571 | ControlAction.RESPONSE_CHALLENGE_PIN -> { |
Matt Pietal | ea87e74 | 2020-03-26 13:28:02 -0400 | [diff] [blame] | 572 | activeDialog = ChallengeDialogs.createPinDialog(cvh, false) |
| 573 | activeDialog?.show() |
| 574 | } |
| 575 | ControlAction.RESPONSE_CHALLENGE_PASSPHRASE -> { |
| 576 | activeDialog = ChallengeDialogs.createPinDialog(cvh, true) |
| 577 | activeDialog?.show() |
| 578 | } |
| 579 | ControlAction.RESPONSE_CHALLENGE_ACK -> { |
| 580 | activeDialog = ChallengeDialogs.createConfirmationDialog(cvh) |
Matt Pietal | b7da66c | 2020-03-06 10:49:31 -0500 | [diff] [blame] | 581 | activeDialog?.show() |
| 582 | } |
| 583 | else -> cvh.actionResponse(response) |
| 584 | } |
| 585 | } |
Matt Pietal | 1aac43b | 2020-02-04 15:43:31 -0500 | [diff] [blame] | 586 | } |
Fabian Kozynski | f10b6ab | 2019-12-27 09:31:04 -0500 | [diff] [blame] | 587 | } |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 588 | |
Matt Pietal | 37cc190 | 2020-02-06 10:25:56 -0500 | [diff] [blame] | 589 | private fun createRow(inflater: LayoutInflater, listView: ViewGroup): ViewGroup { |
| 590 | val row = inflater.inflate(R.layout.controls_row, listView, false) as ViewGroup |
| 591 | listView.addView(row) |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 592 | return row |
| 593 | } |
Matt Pietal | 53a8bbd | 2020-03-05 16:10:34 -0500 | [diff] [blame] | 594 | |
| 595 | private fun findSelectionItem(si: StructureInfo, items: List<SelectionItem>): SelectionItem? = |
| 596 | items.firstOrNull { |
| 597 | it.componentName == si.componentName && it.structure == si.structure |
| 598 | } |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 599 | } |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 600 | |
| 601 | private data class SelectionItem( |
| 602 | val appName: CharSequence, |
| 603 | val structure: CharSequence, |
| 604 | val icon: Drawable, |
| 605 | val componentName: ComponentName |
| 606 | ) { |
| 607 | fun getTitle() = if (structure.isEmpty()) { appName } else { structure } |
| 608 | } |
| 609 | |
| 610 | private class ItemAdapter( |
| 611 | val parentContext: Context, |
| 612 | val resource: Int |
| 613 | ) : ArrayAdapter<SelectionItem>(parentContext, resource) { |
| 614 | |
| 615 | val layoutInflater = LayoutInflater.from(context) |
| 616 | |
| 617 | override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { |
| 618 | val item = getItem(position) |
| 619 | val view = convertView ?: layoutInflater.inflate(resource, parent, false) |
| 620 | view.requireViewById<TextView>(R.id.controls_spinner_item).apply { |
| 621 | setText(item.getTitle()) |
| 622 | } |
| 623 | view.requireViewById<ImageView>(R.id.app_icon).apply { |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 624 | setImageDrawable(item.icon) |
| 625 | } |
| 626 | return view |
| 627 | } |
Matt Pietal | 638253a | 2020-03-02 09:10:43 -0500 | [diff] [blame] | 628 | } |