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 | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 19 | import android.accounts.Account |
| 20 | import android.accounts.AccountManager |
Fabian Kozynski | f10b6ab | 2019-12-27 09:31:04 -0500 | [diff] [blame] | 21 | import android.content.ComponentName |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 22 | import android.content.Context |
| 23 | import android.content.Intent |
| 24 | import android.content.ServiceConnection |
Matt Pietal | 37cc190 | 2020-02-06 10:25:56 -0500 | [diff] [blame] | 25 | import android.graphics.drawable.Drawable |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 26 | import android.os.IBinder |
Fabian Kozynski | f10b6ab | 2019-12-27 09:31:04 -0500 | [diff] [blame] | 27 | import android.service.controls.Control |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 28 | import android.service.controls.TokenProvider |
| 29 | import android.util.Log |
| 30 | import android.view.LayoutInflater |
| 31 | import android.view.View |
| 32 | import android.view.ViewGroup |
Matt Pietal | 37cc190 | 2020-02-06 10:25:56 -0500 | [diff] [blame] | 33 | import android.widget.ImageView |
Matt Pietal | e0661b6 | 2020-01-29 14:35:31 -0500 | [diff] [blame] | 34 | import android.widget.LinearLayout |
| 35 | import android.widget.Space |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 36 | |
Matt Pietal | 37cc190 | 2020-02-06 10:25:56 -0500 | [diff] [blame] | 37 | import com.android.settingslib.widget.CandidateInfo |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 38 | import com.android.systemui.controls.controller.ControlsController |
| 39 | import com.android.systemui.controls.controller.ControlInfo |
Matt Pietal | 37cc190 | 2020-02-06 10:25:56 -0500 | [diff] [blame] | 40 | import com.android.systemui.controls.management.ControlsListingController |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 41 | import com.android.systemui.controls.management.ControlsProviderSelectorActivity |
Matt Pietal | 37cc190 | 2020-02-06 10:25:56 -0500 | [diff] [blame] | 42 | import com.android.systemui.dagger.qualifiers.Background |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 43 | import com.android.systemui.dagger.qualifiers.Main |
| 44 | import com.android.systemui.R |
Matt Pietal | 1aac43b | 2020-02-04 15:43:31 -0500 | [diff] [blame] | 45 | import com.android.systemui.util.concurrency.DelayableExecutor |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 46 | |
| 47 | import dagger.Lazy |
| 48 | |
Matt Pietal | 37cc190 | 2020-02-06 10:25:56 -0500 | [diff] [blame] | 49 | import java.text.Collator |
| 50 | |
Fabian Kozynski | f10b6ab | 2019-12-27 09:31:04 -0500 | [diff] [blame] | 51 | import javax.inject.Inject |
| 52 | import javax.inject.Singleton |
| 53 | |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 54 | // TEMP CODE for MOCK |
| 55 | private const val TOKEN = "https://www.googleapis.com/auth/assistant" |
| 56 | private const val SCOPE = "oauth2:" + TOKEN |
| 57 | private var tokenProviderConnection: TokenProviderConnection? = null |
| 58 | class TokenProviderConnection(val cc: ControlsController, val context: Context) |
| 59 | : ServiceConnection { |
| 60 | private var mTokenProvider: TokenProvider? = null |
| 61 | |
| 62 | override fun onServiceConnected(cName: ComponentName, binder: IBinder) { |
| 63 | Thread({ |
Matt Pietal | 307b1ef | 2020-02-10 07:27:01 -0500 | [diff] [blame] | 64 | Log.i(ControlsUiController.TAG, "TokenProviderConnection connected") |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 65 | mTokenProvider = TokenProvider.Stub.asInterface(binder) |
| 66 | |
| 67 | val mLastAccountName = mTokenProvider?.getAccountName() |
| 68 | |
| 69 | if (mLastAccountName == null || mLastAccountName.isEmpty()) { |
Matt Pietal | 307b1ef | 2020-02-10 07:27:01 -0500 | [diff] [blame] | 70 | Log.e(ControlsUiController.TAG, "NO ACCOUNT IS SET. Open HomeMock app") |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 71 | } else { |
| 72 | mTokenProvider?.setAuthToken(getAuthToken(mLastAccountName)) |
| 73 | cc.subscribeToFavorites() |
| 74 | } |
| 75 | }, "TokenProviderThread").start() |
| 76 | } |
| 77 | |
| 78 | override fun onServiceDisconnected(cName: ComponentName) { |
| 79 | mTokenProvider = null |
| 80 | } |
| 81 | |
| 82 | fun getAuthToken(accountName: String): String? { |
| 83 | val am = AccountManager.get(context) |
| 84 | val accounts = am.getAccountsByType("com.google") |
| 85 | if (accounts == null || accounts.size == 0) { |
Matt Pietal | 307b1ef | 2020-02-10 07:27:01 -0500 | [diff] [blame] | 86 | Log.w(ControlsUiController.TAG, "No com.google accounts found") |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 87 | return null |
| 88 | } |
| 89 | |
| 90 | var account: Account? = null |
| 91 | for (a in accounts) { |
| 92 | if (a.name.equals(accountName)) { |
| 93 | account = a |
| 94 | break |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | if (account == null) { |
| 99 | account = accounts[0] |
| 100 | } |
| 101 | |
| 102 | try { |
| 103 | return am.blockingGetAuthToken(account!!, SCOPE, true) |
| 104 | } catch (e: Throwable) { |
Matt Pietal | 307b1ef | 2020-02-10 07:27:01 -0500 | [diff] [blame] | 105 | Log.e(ControlsUiController.TAG, "Error getting auth token", e) |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 106 | return null |
| 107 | } |
| 108 | } |
| 109 | } |
| 110 | |
Matt Pietal | 1aac43b | 2020-02-04 15:43:31 -0500 | [diff] [blame] | 111 | private data class ControlKey(val componentName: ComponentName, val controlId: String) |
| 112 | |
Fabian Kozynski | f10b6ab | 2019-12-27 09:31:04 -0500 | [diff] [blame] | 113 | @Singleton |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 114 | class ControlsUiControllerImpl @Inject constructor ( |
| 115 | val controlsController: Lazy<ControlsController>, |
| 116 | val context: Context, |
Matt Pietal | 37cc190 | 2020-02-06 10:25:56 -0500 | [diff] [blame] | 117 | @Main val uiExecutor: DelayableExecutor, |
| 118 | @Background val bgExecutor: DelayableExecutor, |
| 119 | val controlsListingController: Lazy<ControlsListingController> |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 120 | ) : ControlsUiController { |
| 121 | |
| 122 | private lateinit var controlInfos: List<ControlInfo> |
Matt Pietal | 1aac43b | 2020-02-04 15:43:31 -0500 | [diff] [blame] | 123 | private val controlsById = mutableMapOf<ControlKey, ControlWithState>() |
| 124 | private val controlViewsById = mutableMapOf<ControlKey, ControlViewHolder>() |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 125 | private lateinit var parent: ViewGroup |
| 126 | |
Fabian Kozynski | 8b54045 | 2020-02-04 15:16:30 -0500 | [diff] [blame] | 127 | override val available: Boolean |
| 128 | get() = controlsController.get().available |
| 129 | |
Matt Pietal | 37cc190 | 2020-02-06 10:25:56 -0500 | [diff] [blame] | 130 | private val listingCallback = object : ControlsListingController.ControlsListingCallback { |
| 131 | override fun onServicesUpdated(candidates: List<CandidateInfo>) { |
| 132 | bgExecutor.execute { |
Fabian Kozynski | 6e51a705 | 2020-02-19 12:54:31 -0500 | [diff] [blame] | 133 | val collator = Collator.getInstance(context.resources.configuration.locales[0]) |
Matt Pietal | 37cc190 | 2020-02-06 10:25:56 -0500 | [diff] [blame] | 134 | val localeComparator = compareBy<CandidateInfo, CharSequence>(collator) { |
| 135 | it.loadLabel() |
| 136 | } |
| 137 | |
| 138 | val mList = candidates.toMutableList() |
| 139 | mList.sortWith(localeComparator) |
| 140 | loadInitialSetupViewIcons(mList.map { it.loadLabel() to it.loadIcon() }) |
| 141 | } |
| 142 | } |
| 143 | } |
| 144 | |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 145 | override fun show(parent: ViewGroup) { |
Matt Pietal | 307b1ef | 2020-02-10 07:27:01 -0500 | [diff] [blame] | 146 | Log.d(ControlsUiController.TAG, "show()") |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 147 | |
| 148 | this.parent = parent |
| 149 | |
| 150 | controlInfos = controlsController.get().getFavoriteControls() |
| 151 | |
| 152 | controlInfos.map { |
| 153 | ControlWithState(it, null) |
Matt Pietal | 1aac43b | 2020-02-04 15:43:31 -0500 | [diff] [blame] | 154 | }.associateByTo(controlsById) { ControlKey(it.ci.component, it.ci.controlId) } |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 155 | |
| 156 | if (controlInfos.isEmpty()) { |
| 157 | showInitialSetupView() |
| 158 | } else { |
| 159 | showControlsView() |
| 160 | } |
| 161 | |
| 162 | // Temp code to pass auth |
| 163 | tokenProviderConnection = TokenProviderConnection(controlsController.get(), context) |
| 164 | val serviceIntent = Intent() |
| 165 | serviceIntent.setComponent(ComponentName("com.android.systemui.home.mock", |
| 166 | "com.android.systemui.home.mock.AuthService")) |
Matt Pietal | a635bd1 | 2020-02-03 13:36:18 -0500 | [diff] [blame] | 167 | if (!context.bindService(serviceIntent, tokenProviderConnection!!, |
| 168 | Context.BIND_AUTO_CREATE)) { |
| 169 | controlsController.get().subscribeToFavorites() |
| 170 | } |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 171 | } |
| 172 | |
| 173 | private fun showInitialSetupView() { |
| 174 | val inflater = LayoutInflater.from(context) |
| 175 | inflater.inflate(R.layout.controls_no_favorites, parent, true) |
| 176 | |
Matt Pietal | 37cc190 | 2020-02-06 10:25:56 -0500 | [diff] [blame] | 177 | val viewGroup = parent.requireViewById(R.id.controls_no_favorites_group) as ViewGroup |
| 178 | viewGroup.setOnClickListener(launchSelectorActivityListener(context)) |
| 179 | |
| 180 | controlsListingController.get().addCallback(listingCallback) |
| 181 | } |
| 182 | |
| 183 | private fun loadInitialSetupViewIcons(icons: List<Pair<CharSequence, Drawable>>) { |
| 184 | uiExecutor.execute { |
| 185 | val viewGroup = parent.requireViewById(R.id.controls_icon_row) as ViewGroup |
| 186 | viewGroup.removeAllViews() |
| 187 | |
| 188 | val inflater = LayoutInflater.from(context) |
| 189 | icons.forEach { |
| 190 | val imageView = inflater.inflate(R.layout.controls_icon, viewGroup, false) |
| 191 | as ImageView |
| 192 | imageView.setContentDescription(it.first) |
| 193 | imageView.setImageDrawable(it.second) |
| 194 | viewGroup.addView(imageView) |
| 195 | } |
| 196 | } |
Matt Pietal | e0661b6 | 2020-01-29 14:35:31 -0500 | [diff] [blame] | 197 | } |
| 198 | |
| 199 | private fun launchSelectorActivityListener(context: Context): (View) -> Unit { |
| 200 | return { _ -> |
| 201 | val closeDialog = Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS) |
| 202 | context.sendBroadcast(closeDialog) |
| 203 | |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 204 | val i = Intent() |
| 205 | i.setComponent(ComponentName(context, ControlsProviderSelectorActivity::class.java)) |
Matt Pietal | e0661b6 | 2020-01-29 14:35:31 -0500 | [diff] [blame] | 206 | i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 207 | context.startActivity(i) |
| 208 | } |
| 209 | } |
| 210 | |
| 211 | private fun showControlsView() { |
| 212 | val inflater = LayoutInflater.from(context) |
| 213 | inflater.inflate(R.layout.controls_with_favorites, parent, true) |
| 214 | |
| 215 | val listView = parent.requireViewById(R.id.global_actions_controls_list) as ViewGroup |
| 216 | var lastRow: ViewGroup = createRow(inflater, listView) |
| 217 | controlInfos.forEach { |
Matt Pietal | 307b1ef | 2020-02-10 07:27:01 -0500 | [diff] [blame] | 218 | Log.d(ControlsUiController.TAG, "favorited control id: " + it.controlId) |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 219 | if (lastRow.getChildCount() == 2) { |
| 220 | lastRow = createRow(inflater, listView) |
| 221 | } |
| 222 | val item = inflater.inflate( |
| 223 | R.layout.controls_base_item, lastRow, false) as ViewGroup |
| 224 | lastRow.addView(item) |
Matt Pietal | 5bdfba4 | 2020-02-14 09:14:43 -0500 | [diff] [blame] | 225 | val cvh = ControlViewHolder(item, controlsController.get(), uiExecutor, bgExecutor) |
Matt Pietal | 1aac43b | 2020-02-04 15:43:31 -0500 | [diff] [blame] | 226 | val key = ControlKey(it.component, it.controlId) |
| 227 | cvh.bindData(controlsById.getValue(key)) |
| 228 | controlViewsById.put(key, cvh) |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 229 | } |
| 230 | |
Matt Pietal | e0661b6 | 2020-01-29 14:35:31 -0500 | [diff] [blame] | 231 | if ((controlInfos.size % 2) == 1) { |
| 232 | lastRow.addView(Space(context), LinearLayout.LayoutParams(0, 0, 1f)) |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 233 | } |
Matt Pietal | e0661b6 | 2020-01-29 14:35:31 -0500 | [diff] [blame] | 234 | |
| 235 | val moreImageView = parent.requireViewById(R.id.controls_more) as View |
| 236 | moreImageView.setOnClickListener(launchSelectorActivityListener(context)) |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 237 | } |
| 238 | |
| 239 | override fun hide() { |
Matt Pietal | 307b1ef | 2020-02-10 07:27:01 -0500 | [diff] [blame] | 240 | Log.d(ControlsUiController.TAG, "hide()") |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 241 | controlsController.get().unsubscribe() |
| 242 | context.unbindService(tokenProviderConnection) |
| 243 | tokenProviderConnection = null |
| 244 | |
| 245 | parent.removeAllViews() |
| 246 | controlsById.clear() |
| 247 | controlViewsById.clear() |
Matt Pietal | 37cc190 | 2020-02-06 10:25:56 -0500 | [diff] [blame] | 248 | controlsListingController.get().removeCallback(listingCallback) |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 249 | } |
Fabian Kozynski | f10b6ab | 2019-12-27 09:31:04 -0500 | [diff] [blame] | 250 | |
| 251 | override fun onRefreshState(componentName: ComponentName, controls: List<Control>) { |
Matt Pietal | 307b1ef | 2020-02-10 07:27:01 -0500 | [diff] [blame] | 252 | Log.d(ControlsUiController.TAG, "onRefreshState()") |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 253 | controls.forEach { c -> |
Matt Pietal | 1aac43b | 2020-02-04 15:43:31 -0500 | [diff] [blame] | 254 | controlsById.get(ControlKey(componentName, c.getControlId()))?.let { |
Matt Pietal | 307b1ef | 2020-02-10 07:27:01 -0500 | [diff] [blame] | 255 | Log.d(ControlsUiController.TAG, "onRefreshState() for id: " + c.getControlId()) |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 256 | val cws = ControlWithState(it.ci, c) |
Matt Pietal | 1aac43b | 2020-02-04 15:43:31 -0500 | [diff] [blame] | 257 | val key = ControlKey(componentName, c.getControlId()) |
| 258 | controlsById.put(key, cws) |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 259 | |
| 260 | uiExecutor.execute { |
Matt Pietal | 1aac43b | 2020-02-04 15:43:31 -0500 | [diff] [blame] | 261 | controlViewsById.get(key)?.bindData(cws) |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 262 | } |
| 263 | } |
| 264 | } |
Fabian Kozynski | f10b6ab | 2019-12-27 09:31:04 -0500 | [diff] [blame] | 265 | } |
| 266 | |
| 267 | override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) { |
Matt Pietal | 1aac43b | 2020-02-04 15:43:31 -0500 | [diff] [blame] | 268 | val key = ControlKey(componentName, controlId) |
| 269 | uiExecutor.execute { |
| 270 | controlViewsById.get(key)?.actionResponse(response) |
| 271 | } |
Fabian Kozynski | f10b6ab | 2019-12-27 09:31:04 -0500 | [diff] [blame] | 272 | } |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 273 | |
Matt Pietal | 37cc190 | 2020-02-06 10:25:56 -0500 | [diff] [blame] | 274 | private fun createRow(inflater: LayoutInflater, listView: ViewGroup): ViewGroup { |
| 275 | val row = inflater.inflate(R.layout.controls_row, listView, false) as ViewGroup |
| 276 | listView.addView(row) |
Matt Pietal | 2223179 | 2020-01-23 09:51:09 -0500 | [diff] [blame] | 277 | return row |
| 278 | } |
| 279 | } |