blob: f4fd37557fa474eababb7ff99bc0b80b2bfe124c [file] [log] [blame]
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -05001/*
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
17package com.android.systemui.controls.ui
18
Matt Pietal22231792020-01-23 09:51:09 -050019import android.accounts.Account
20import android.accounts.AccountManager
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -050021import android.content.ComponentName
Matt Pietal22231792020-01-23 09:51:09 -050022import android.content.Context
23import android.content.Intent
24import android.content.ServiceConnection
Matt Pietal37cc1902020-02-06 10:25:56 -050025import android.graphics.drawable.Drawable
Matt Pietal22231792020-01-23 09:51:09 -050026import android.os.IBinder
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -050027import android.service.controls.Control
Matt Pietal22231792020-01-23 09:51:09 -050028import android.service.controls.TokenProvider
29import android.util.Log
30import android.view.LayoutInflater
31import android.view.View
32import android.view.ViewGroup
Matt Pietal37cc1902020-02-06 10:25:56 -050033import android.widget.ImageView
Matt Pietale0661b62020-01-29 14:35:31 -050034import android.widget.LinearLayout
35import android.widget.Space
Matt Pietal22231792020-01-23 09:51:09 -050036
Matt Pietal37cc1902020-02-06 10:25:56 -050037import com.android.settingslib.widget.CandidateInfo
Matt Pietal22231792020-01-23 09:51:09 -050038import com.android.systemui.controls.controller.ControlsController
39import com.android.systemui.controls.controller.ControlInfo
Matt Pietal37cc1902020-02-06 10:25:56 -050040import com.android.systemui.controls.management.ControlsListingController
Matt Pietal22231792020-01-23 09:51:09 -050041import com.android.systemui.controls.management.ControlsProviderSelectorActivity
Matt Pietal37cc1902020-02-06 10:25:56 -050042import com.android.systemui.dagger.qualifiers.Background
Matt Pietal22231792020-01-23 09:51:09 -050043import com.android.systemui.dagger.qualifiers.Main
44import com.android.systemui.R
Matt Pietal1aac43b2020-02-04 15:43:31 -050045import com.android.systemui.util.concurrency.DelayableExecutor
Matt Pietal22231792020-01-23 09:51:09 -050046
47import dagger.Lazy
48
Matt Pietal37cc1902020-02-06 10:25:56 -050049import java.text.Collator
50
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -050051import javax.inject.Inject
52import javax.inject.Singleton
53
Matt Pietal22231792020-01-23 09:51:09 -050054// TEMP CODE for MOCK
55private const val TOKEN = "https://www.googleapis.com/auth/assistant"
56private const val SCOPE = "oauth2:" + TOKEN
57private var tokenProviderConnection: TokenProviderConnection? = null
58class 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 Pietal307b1ef2020-02-10 07:27:01 -050064 Log.i(ControlsUiController.TAG, "TokenProviderConnection connected")
Matt Pietal22231792020-01-23 09:51:09 -050065 mTokenProvider = TokenProvider.Stub.asInterface(binder)
66
67 val mLastAccountName = mTokenProvider?.getAccountName()
68
69 if (mLastAccountName == null || mLastAccountName.isEmpty()) {
Matt Pietal307b1ef2020-02-10 07:27:01 -050070 Log.e(ControlsUiController.TAG, "NO ACCOUNT IS SET. Open HomeMock app")
Matt Pietal22231792020-01-23 09:51:09 -050071 } 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 Pietal307b1ef2020-02-10 07:27:01 -050086 Log.w(ControlsUiController.TAG, "No com.google accounts found")
Matt Pietal22231792020-01-23 09:51:09 -050087 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 Pietal307b1ef2020-02-10 07:27:01 -0500105 Log.e(ControlsUiController.TAG, "Error getting auth token", e)
Matt Pietal22231792020-01-23 09:51:09 -0500106 return null
107 }
108 }
109}
110
Matt Pietal1aac43b2020-02-04 15:43:31 -0500111private data class ControlKey(val componentName: ComponentName, val controlId: String)
112
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500113@Singleton
Matt Pietal22231792020-01-23 09:51:09 -0500114class ControlsUiControllerImpl @Inject constructor (
115 val controlsController: Lazy<ControlsController>,
116 val context: Context,
Matt Pietal37cc1902020-02-06 10:25:56 -0500117 @Main val uiExecutor: DelayableExecutor,
118 @Background val bgExecutor: DelayableExecutor,
119 val controlsListingController: Lazy<ControlsListingController>
Matt Pietal22231792020-01-23 09:51:09 -0500120) : ControlsUiController {
121
122 private lateinit var controlInfos: List<ControlInfo>
Matt Pietal1aac43b2020-02-04 15:43:31 -0500123 private val controlsById = mutableMapOf<ControlKey, ControlWithState>()
124 private val controlViewsById = mutableMapOf<ControlKey, ControlViewHolder>()
Matt Pietal22231792020-01-23 09:51:09 -0500125 private lateinit var parent: ViewGroup
126
Fabian Kozynski8b540452020-02-04 15:16:30 -0500127 override val available: Boolean
128 get() = controlsController.get().available
129
Matt Pietal37cc1902020-02-06 10:25:56 -0500130 private val listingCallback = object : ControlsListingController.ControlsListingCallback {
131 override fun onServicesUpdated(candidates: List<CandidateInfo>) {
132 bgExecutor.execute {
Fabian Kozynski6e51a7052020-02-19 12:54:31 -0500133 val collator = Collator.getInstance(context.resources.configuration.locales[0])
Matt Pietal37cc1902020-02-06 10:25:56 -0500134 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 Pietal22231792020-01-23 09:51:09 -0500145 override fun show(parent: ViewGroup) {
Matt Pietal307b1ef2020-02-10 07:27:01 -0500146 Log.d(ControlsUiController.TAG, "show()")
Matt Pietal22231792020-01-23 09:51:09 -0500147
148 this.parent = parent
149
150 controlInfos = controlsController.get().getFavoriteControls()
151
152 controlInfos.map {
153 ControlWithState(it, null)
Matt Pietal1aac43b2020-02-04 15:43:31 -0500154 }.associateByTo(controlsById) { ControlKey(it.ci.component, it.ci.controlId) }
Matt Pietal22231792020-01-23 09:51:09 -0500155
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 Pietala635bd12020-02-03 13:36:18 -0500167 if (!context.bindService(serviceIntent, tokenProviderConnection!!,
168 Context.BIND_AUTO_CREATE)) {
169 controlsController.get().subscribeToFavorites()
170 }
Matt Pietal22231792020-01-23 09:51:09 -0500171 }
172
173 private fun showInitialSetupView() {
174 val inflater = LayoutInflater.from(context)
175 inflater.inflate(R.layout.controls_no_favorites, parent, true)
176
Matt Pietal37cc1902020-02-06 10:25:56 -0500177 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 Pietale0661b62020-01-29 14:35:31 -0500197 }
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 Pietal22231792020-01-23 09:51:09 -0500204 val i = Intent()
205 i.setComponent(ComponentName(context, ControlsProviderSelectorActivity::class.java))
Matt Pietale0661b62020-01-29 14:35:31 -0500206 i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
Matt Pietal22231792020-01-23 09:51:09 -0500207 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 Pietal307b1ef2020-02-10 07:27:01 -0500218 Log.d(ControlsUiController.TAG, "favorited control id: " + it.controlId)
Matt Pietal22231792020-01-23 09:51:09 -0500219 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 Pietal5bdfba42020-02-14 09:14:43 -0500225 val cvh = ControlViewHolder(item, controlsController.get(), uiExecutor, bgExecutor)
Matt Pietal1aac43b2020-02-04 15:43:31 -0500226 val key = ControlKey(it.component, it.controlId)
227 cvh.bindData(controlsById.getValue(key))
228 controlViewsById.put(key, cvh)
Matt Pietal22231792020-01-23 09:51:09 -0500229 }
230
Matt Pietale0661b62020-01-29 14:35:31 -0500231 if ((controlInfos.size % 2) == 1) {
232 lastRow.addView(Space(context), LinearLayout.LayoutParams(0, 0, 1f))
Matt Pietal22231792020-01-23 09:51:09 -0500233 }
Matt Pietale0661b62020-01-29 14:35:31 -0500234
235 val moreImageView = parent.requireViewById(R.id.controls_more) as View
236 moreImageView.setOnClickListener(launchSelectorActivityListener(context))
Matt Pietal22231792020-01-23 09:51:09 -0500237 }
238
239 override fun hide() {
Matt Pietal307b1ef2020-02-10 07:27:01 -0500240 Log.d(ControlsUiController.TAG, "hide()")
Matt Pietal22231792020-01-23 09:51:09 -0500241 controlsController.get().unsubscribe()
242 context.unbindService(tokenProviderConnection)
243 tokenProviderConnection = null
244
245 parent.removeAllViews()
246 controlsById.clear()
247 controlViewsById.clear()
Matt Pietal37cc1902020-02-06 10:25:56 -0500248 controlsListingController.get().removeCallback(listingCallback)
Matt Pietal22231792020-01-23 09:51:09 -0500249 }
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500250
251 override fun onRefreshState(componentName: ComponentName, controls: List<Control>) {
Matt Pietal307b1ef2020-02-10 07:27:01 -0500252 Log.d(ControlsUiController.TAG, "onRefreshState()")
Matt Pietal22231792020-01-23 09:51:09 -0500253 controls.forEach { c ->
Matt Pietal1aac43b2020-02-04 15:43:31 -0500254 controlsById.get(ControlKey(componentName, c.getControlId()))?.let {
Matt Pietal307b1ef2020-02-10 07:27:01 -0500255 Log.d(ControlsUiController.TAG, "onRefreshState() for id: " + c.getControlId())
Matt Pietal22231792020-01-23 09:51:09 -0500256 val cws = ControlWithState(it.ci, c)
Matt Pietal1aac43b2020-02-04 15:43:31 -0500257 val key = ControlKey(componentName, c.getControlId())
258 controlsById.put(key, cws)
Matt Pietal22231792020-01-23 09:51:09 -0500259
260 uiExecutor.execute {
Matt Pietal1aac43b2020-02-04 15:43:31 -0500261 controlViewsById.get(key)?.bindData(cws)
Matt Pietal22231792020-01-23 09:51:09 -0500262 }
263 }
264 }
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500265 }
266
267 override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) {
Matt Pietal1aac43b2020-02-04 15:43:31 -0500268 val key = ControlKey(componentName, controlId)
269 uiExecutor.execute {
270 controlViewsById.get(key)?.actionResponse(response)
271 }
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500272 }
Matt Pietal22231792020-01-23 09:51:09 -0500273
Matt Pietal37cc1902020-02-06 10:25:56 -0500274 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 Pietal22231792020-01-23 09:51:09 -0500277 return row
278 }
279}