1 /*
<lambda>null2 * Copyright (C) 2019 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.statusbar.notification.people
18
19 import android.content.Context
20 import android.database.ContentObserver
21 import android.net.Uri
22 import android.os.Handler
23 import android.os.UserHandle
24 import android.provider.Settings
25 import android.view.View
26 import com.android.systemui.dagger.qualifiers.Main
27 import com.android.systemui.plugins.ActivityStarter
28 import javax.inject.Inject
29 import javax.inject.Singleton
30
31 /** Boundary between the View and PeopleHub, as seen by the View. */
32 interface PeopleHubViewAdapter {
33 fun bindView(viewBoundary: PeopleHubViewBoundary): Subscription
34 }
35
36 /** Abstract `View` representation of PeopleHub. */
37 interface PeopleHubViewBoundary {
38 /** View used for animating the activity launch caused by clicking a person in the hub. */
39 val associatedViewForClickAnimation: View
40
41 /**
42 * [DataListener]s for individual people in the hub.
43 *
44 * These listeners should be ordered such that the first element will be bound to the most
45 * recent person to be added to the hub, and then continuing in descending order. If there are
46 * not enough people to satisfy each listener, `null` will be passed instead, indicating that
47 * the `View` should render a placeholder.
48 */
49 val personViewAdapters: Sequence<DataListener<PersonViewModel?>>
50
51 /** Sets the visibility of the Hub in the notification shade. */
setVisiblenull52 fun setVisible(isVisible: Boolean)
53 }
54
55 /** Creates a [PeopleHubViewModel] given some additional information required from the `View`. */
56 interface PeopleHubViewModelFactory {
57
58 /**
59 * Creates a [PeopleHubViewModel] that, when clicked, starts an activity using an animation
60 * involving the given [view].
61 */
62 fun createWithAssociatedClickView(view: View): PeopleHubViewModel
63 }
64
65 /**
66 * Wraps a [PeopleHubViewBoundary] in a [DataListener], and connects it to the data
67 * pipeline.
68 *
69 * @param dataSource PeopleHub data pipeline.
70 */
71 @Singleton
72 class PeopleHubViewAdapterImpl @Inject constructor(
73 private val dataSource: DataSource<@JvmSuppressWildcards PeopleHubViewModelFactory>
74 ) : PeopleHubViewAdapter {
75
bindViewnull76 override fun bindView(viewBoundary: PeopleHubViewBoundary): Subscription =
77 dataSource.registerListener(PeopleHubDataListenerImpl(viewBoundary))
78 }
79
80 private class PeopleHubDataListenerImpl(
81 private val viewBoundary: PeopleHubViewBoundary
82 ) : DataListener<PeopleHubViewModelFactory> {
83
84 override fun onDataChanged(data: PeopleHubViewModelFactory) {
85 val viewModel = data.createWithAssociatedClickView(
86 viewBoundary.associatedViewForClickAnimation
87 )
88 viewBoundary.setVisible(viewModel.isVisible)
89 val padded = viewModel.people + repeated(null)
90 for ((adapter, model) in viewBoundary.personViewAdapters.zip(padded)) {
91 adapter.onDataChanged(model)
92 }
93 }
94 }
95
96 /**
97 * Converts [PeopleHubModel]s into [PeopleHubViewModelFactory]s.
98 *
99 * This class serves as the glue between the View layer (which depends on
100 * [PeopleHubViewBoundary]) and the Data layer (which produces [PeopleHubModel]s).
101 */
102 @Singleton
103 class PeopleHubViewModelFactoryDataSourceImpl @Inject constructor(
104 private val activityStarter: ActivityStarter,
105 private val dataSource: DataSource<@JvmSuppressWildcards PeopleHubModel>
106 ) : DataSource<PeopleHubViewModelFactory> {
107
registerListenernull108 override fun registerListener(listener: DataListener<PeopleHubViewModelFactory>): Subscription {
109 var model: PeopleHubModel? = null
110
111 fun updateListener() {
112 // don't invoke listener until we've received our first model
113 model?.let { model ->
114 val factory = PeopleHubViewModelFactoryImpl(model, activityStarter)
115 listener.onDataChanged(factory)
116 }
117 }
118 val dataSub = dataSource.registerListener(object : DataListener<PeopleHubModel> {
119 override fun onDataChanged(data: PeopleHubModel) {
120 model = data
121 updateListener()
122 }
123 })
124 return object : Subscription {
125 override fun unsubscribe() {
126 dataSub.unsubscribe()
127 }
128 }
129 }
130 }
131
132 private object EmptyViewModelFactory : PeopleHubViewModelFactory {
createWithAssociatedClickViewnull133 override fun createWithAssociatedClickView(view: View): PeopleHubViewModel {
134 return PeopleHubViewModel(emptySequence(), false)
135 }
136 }
137
138 private class PeopleHubViewModelFactoryImpl(
139 private val model: PeopleHubModel,
140 private val activityStarter: ActivityStarter
141 ) : PeopleHubViewModelFactory {
142
createWithAssociatedClickViewnull143 override fun createWithAssociatedClickView(view: View): PeopleHubViewModel {
144 val personViewModels = model.people.asSequence().map { personModel ->
145 val onClick = {
146 personModel.clickRunnable.run()
147 }
148 PersonViewModel(personModel.name, personModel.avatar, onClick)
149 }
150 return PeopleHubViewModel(personViewModels, model.people.isNotEmpty())
151 }
152 }
153
154 @Singleton
155 class PeopleHubSettingChangeDataSourceImpl @Inject constructor(
156 @Main private val handler: Handler,
157 context: Context
158 ) : DataSource<Boolean> {
159
160 private val settingUri = Settings.Secure.getUriFor(Settings.Secure.PEOPLE_STRIP)
161 private val contentResolver = context.contentResolver
162
registerListenernull163 override fun registerListener(listener: DataListener<Boolean>): Subscription {
164 // Immediately report current value of setting
165 updateListener(listener)
166 val observer = object : ContentObserver(handler) {
167 override fun onChange(selfChange: Boolean, uri: Uri?, flags: Int) {
168 super.onChange(selfChange, uri, flags)
169 updateListener(listener)
170 }
171 }
172 contentResolver.registerContentObserver(settingUri, false, observer, UserHandle.USER_ALL)
173 return object : Subscription {
174 override fun unsubscribe() = contentResolver.unregisterContentObserver(observer)
175 }
176 }
177
updateListenernull178 private fun updateListener(listener: DataListener<Boolean>) {
179 val setting = Settings.Secure.getIntForUser(
180 contentResolver,
181 Settings.Secure.PEOPLE_STRIP,
182 0,
183 UserHandle.USER_CURRENT
184 )
185 listener.onDataChanged(setting != 0)
186 }
187 }
188
<lambda>null189 private fun <T> repeated(value: T): Sequence<T> = sequence {
190 while (true) {
191 yield(value)
192 }
193 }