1 /* <lambda>null2 * Copyright (C) 2023 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.footer.ui.viewbinder 18 19 import android.view.View 20 import androidx.lifecycle.lifecycleScope 21 import com.android.app.tracing.coroutines.launchTraced as launch 22 import com.android.internal.jank.InteractionJankMonitor 23 import com.android.systemui.lifecycle.repeatWhenAttached 24 import com.android.systemui.statusbar.notification.NotificationActivityStarter 25 import com.android.systemui.statusbar.notification.NotificationActivityStarter.SettingsIntent 26 import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix 27 import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter 28 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView 29 import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel 30 import com.android.systemui.util.ui.isAnimating 31 import com.android.systemui.util.ui.stopAnimating 32 import com.android.systemui.util.ui.value 33 import kotlinx.coroutines.DisposableHandle 34 import kotlinx.coroutines.coroutineScope 35 36 /** Binds a [FooterView] to its [view model][FooterViewModel]. */ 37 object FooterViewBinder { 38 fun bindWhileAttached( 39 footer: FooterView, 40 viewModel: FooterViewModel, 41 clearAllNotifications: View.OnClickListener, 42 launchNotificationSettings: View.OnClickListener, 43 launchNotificationHistory: View.OnClickListener, 44 notificationActivityStarter: NotificationActivityStarter, 45 ): DisposableHandle { 46 return footer.repeatWhenAttached { 47 lifecycleScope.launch { 48 bind( 49 footer, 50 viewModel, 51 clearAllNotifications, 52 launchNotificationSettings, 53 launchNotificationHistory, 54 notificationActivityStarter, 55 ) 56 } 57 } 58 } 59 60 suspend fun bind( 61 footer: FooterView, 62 viewModel: FooterViewModel, 63 clearAllNotifications: View.OnClickListener, 64 launchNotificationSettings: View.OnClickListener, 65 launchNotificationHistory: View.OnClickListener, 66 notificationActivityStarter: NotificationActivityStarter, 67 ) = coroutineScope { 68 launch { bindClearAllButton(footer, viewModel, clearAllNotifications) } 69 if (!NotifRedesignFooter.isEnabled) { 70 launch { 71 bindManageOrHistoryButton( 72 footer, 73 viewModel, 74 launchNotificationSettings, 75 launchNotificationHistory, 76 notificationActivityStarter, 77 ) 78 } 79 } else { 80 launch { bindSettingsButton(footer, viewModel, notificationActivityStarter) } 81 launch { bindHistoryButton(footer, viewModel, notificationActivityStarter) } 82 } 83 launch { bindMessage(footer, viewModel) } 84 } 85 86 private suspend fun bindClearAllButton( 87 footer: FooterView, 88 viewModel: FooterViewModel, 89 clearAllNotifications: View.OnClickListener, 90 ) = coroutineScope { 91 launch { 92 viewModel.clearAllButton.labelId.collect { textId -> 93 footer.setClearAllButtonText(textId) 94 } 95 } 96 97 launch { 98 viewModel.clearAllButton.accessibilityDescriptionId.collect { textId -> 99 footer.setClearAllButtonDescription(textId) 100 } 101 } 102 103 launch { 104 viewModel.clearAllButton.isVisible.collect { isVisible -> 105 if (isVisible.value) { 106 footer.setClearAllButtonClickListener(clearAllNotifications) 107 } else { 108 // When the button isn't visible, it also shouldn't react to clicks. This is 109 // necessary because when the clear all button is not visible, it's actually 110 // just the alpha that becomes 0 so it can still be tapped. 111 footer.setClearAllButtonClickListener(null) 112 } 113 114 if (isVisible.isAnimating) { 115 footer.setClearAllButtonVisible(isVisible.value, /* animate= */ true) { _ -> 116 isVisible.stopAnimating() 117 } 118 } else { 119 footer.setClearAllButtonVisible(isVisible.value, /* animate= */ false) 120 } 121 } 122 } 123 } 124 125 private suspend fun bindSettingsButton( 126 footer: FooterView, 127 viewModel: FooterViewModel, 128 notificationActivityStarter: NotificationActivityStarter, 129 ) = coroutineScope { 130 val settingsIntent = 131 SettingsIntent.forNotificationSettings( 132 cujType = InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON 133 ) 134 val onClickListener = { view: View -> 135 notificationActivityStarter.startSettingsIntent(view, settingsIntent) 136 } 137 footer.setSettingsButtonClickListener(onClickListener) 138 139 launch { 140 // NOTE: This visibility change is never animated. We also don't need to do anything 141 // special about the onClickListener here, since we're changing the visibility to 142 // GONE so it won't be clickable anyway. 143 viewModel.settingsButtonVisible.collect { footer.setSettingsButtonVisible(it) } 144 } 145 } 146 147 private suspend fun bindHistoryButton( 148 footer: FooterView, 149 viewModel: FooterViewModel, 150 notificationActivityStarter: NotificationActivityStarter, 151 ) = coroutineScope { 152 val settingsIntent = 153 SettingsIntent.forNotificationHistory( 154 cujType = InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON 155 ) 156 val onClickListener = { view: View -> 157 notificationActivityStarter.startSettingsIntent(view, settingsIntent) 158 } 159 footer.setHistoryButtonClickListener(onClickListener) 160 161 launch { 162 // NOTE: This visibility change is never animated. We also don't need to do anything 163 // special about the onClickListener here, since we're changing the visibility to 164 // GONE so it won't be clickable anyway. 165 viewModel.historyButtonVisible.collect { footer.setHistoryButtonVisible(it) } 166 } 167 } 168 169 private suspend fun bindManageOrHistoryButton( 170 footer: FooterView, 171 viewModel: FooterViewModel, 172 launchNotificationSettings: View.OnClickListener, 173 launchNotificationHistory: View.OnClickListener, 174 notificationActivityStarter: NotificationActivityStarter, 175 ) = coroutineScope { 176 launch { 177 if (ModesEmptyShadeFix.isEnabled) { 178 viewModel.manageOrHistoryButtonClick.collect { settingsIntent -> 179 val onClickListener = { view: View -> 180 notificationActivityStarter.startSettingsIntent(view, settingsIntent) 181 } 182 footer.setManageButtonClickListener(onClickListener) 183 } 184 } else { 185 viewModel.manageButtonShouldLaunchHistory.collect { shouldLaunchHistory -> 186 if (shouldLaunchHistory) { 187 footer.setManageButtonClickListener(launchNotificationHistory) 188 } else { 189 footer.setManageButtonClickListener(launchNotificationSettings) 190 } 191 } 192 } 193 } 194 195 launch { 196 viewModel.manageOrHistoryButton.labelId.collect { textId -> 197 footer.setManageOrHistoryButtonText(textId) 198 } 199 } 200 201 launch { 202 viewModel.manageOrHistoryButton.accessibilityDescriptionId.collect { textId -> 203 footer.setManageOrHistoryButtonDescription(textId) 204 } 205 } 206 207 launch { 208 viewModel.manageOrHistoryButton.isVisible.collect { isVisible -> 209 // NOTE: This visibility change is never animated. We also don't need to do anything 210 // special about the onClickListener here, since we're changing the visibility to 211 // GONE so it won't be clickable anyway. 212 footer.setManageOrHistoryButtonVisible(isVisible.value) 213 } 214 } 215 } 216 217 private suspend fun bindMessage(footer: FooterView, viewModel: FooterViewModel) = 218 coroutineScope { 219 // Bind the resource IDs 220 footer.setMessageString(viewModel.message.messageId) 221 footer.setMessageIcon(viewModel.message.iconId) 222 223 launch { 224 viewModel.message.isVisible.collect { visible -> 225 footer.setFooterLabelVisible(visible) 226 } 227 } 228 } 229 } 230