1 /* 2 * 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.SysUISingleton 27 import com.android.systemui.dagger.qualifiers.Main 28 import com.android.systemui.plugins.ActivityStarter 29 import javax.inject.Inject 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. */ 52 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 @SysUISingleton 72 class PeopleHubViewAdapterImpl @Inject constructor( 73 private val dataSource: DataSource<@JvmSuppressWildcards PeopleHubViewModelFactory> 74 ) : PeopleHubViewAdapter { 75 76 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 @SysUISingleton 103 class PeopleHubViewModelFactoryDataSourceImpl @Inject constructor( 104 private val activityStarter: ActivityStarter, 105 private val dataSource: DataSource<@JvmSuppressWildcards PeopleHubModel> 106 ) : DataSource<PeopleHubViewModelFactory> { 107 108 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 { 133 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 143 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 @SysUISingleton 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 163 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 178 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 189 private fun <T> repeated(value: T): Sequence<T> = sequence { 190 while (true) { 191 yield(value) 192 } 193 }