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 }