1 /* 2 * Copyright (C) 2021 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.phone 18 19 import android.view.View 20 import androidx.constraintlayout.motion.widget.MotionLayout 21 import com.android.systemui.R 22 import com.android.systemui.animation.ShadeInterpolation 23 import com.android.systemui.battery.BatteryMeterView 24 import com.android.systemui.battery.BatteryMeterViewController 25 import com.android.systemui.flags.FeatureFlags 26 import com.android.systemui.qs.HeaderPrivacyIconsController 27 import com.android.systemui.qs.carrier.QSCarrierGroupController 28 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent.StatusBarScope 29 import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.SPLIT_SHADE_BATTERY_CONTROLLER 30 import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.SPLIT_SHADE_HEADER 31 import javax.inject.Inject 32 import javax.inject.Named 33 34 @StatusBarScope 35 class SplitShadeHeaderController @Inject constructor( 36 @Named(SPLIT_SHADE_HEADER) private val statusBar: View, 37 private val statusBarIconController: StatusBarIconController, 38 private val privacyIconsController: HeaderPrivacyIconsController, 39 qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder, 40 featureFlags: FeatureFlags, 41 @Named(SPLIT_SHADE_BATTERY_CONTROLLER) batteryMeterViewController: BatteryMeterViewController 42 ) { 43 44 companion object { 45 private val HEADER_TRANSITION_ID = R.id.header_transition 46 private val SPLIT_HEADER_TRANSITION_ID = R.id.split_header_transition 47 } 48 49 private val carrierIconSlots: List<String> 50 private val combinedHeaders = featureFlags.useCombinedQSHeaders() 51 private val iconManager: StatusBarIconController.IconManager 52 private val qsCarrierGroupController: QSCarrierGroupController 53 private val iconContainer: StatusIconContainer 54 private var visible = false 55 set(value) { 56 if (field == value) { 57 return 58 } 59 field = value 60 updateListeners() 61 } 62 63 var shadeExpanded = false 64 set(value) { 65 if (field == value) { 66 return 67 } 68 field = value 69 onShadeExpandedChanged() 70 } 71 72 var splitShadeMode = false 73 set(value) { 74 if (field == value) { 75 return 76 } 77 field = value 78 onSplitShadeModeChanged() 79 } 80 81 var shadeExpandedFraction = -1f 82 set(value) { 83 if (visible && field != value) { 84 statusBar.alpha = ShadeInterpolation.getContentAlpha(value) 85 field = value 86 } 87 } 88 89 var qsExpandedFraction = -1f 90 set(value) { 91 if (visible && field != value) { 92 field = value 93 updateVisibility() 94 updatePosition() 95 } 96 } 97 98 init { 99 if (statusBar is MotionLayout) { 100 val context = statusBar.context 101 val resources = statusBar.resources 102 statusBar.getConstraintSet(R.id.qqs_header_constraint) 103 .load(context, resources.getXml(R.xml.qqs_header)) 104 statusBar.getConstraintSet(R.id.qs_header_constraint) 105 .load(context, resources.getXml(R.xml.qs_header)) 106 statusBar.getConstraintSet(R.id.split_header_constraint) 107 .load(context, resources.getXml(R.xml.split_header)) 108 } 109 } 110 111 init { 112 batteryMeterViewController.init() 113 val batteryIcon: BatteryMeterView = statusBar.findViewById(R.id.batteryRemainingIcon) 114 115 // battery settings same as in QS icons 116 batteryMeterViewController.ignoreTunerUpdates() 117 batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE) 118 119 carrierIconSlots = if (featureFlags.isCombinedStatusBarSignalIconsEnabled) { 120 listOf( 121 statusBar.context.getString(com.android.internal.R.string.status_bar_no_calling), 122 statusBar.context.getString(com.android.internal.R.string.status_bar_call_strength) 123 ) 124 } else { 125 listOf(statusBar.context.getString(com.android.internal.R.string.status_bar_mobile)) 126 } 127 128 iconContainer = statusBar.findViewById(R.id.statusIcons) 129 iconManager = StatusBarIconController.IconManager(iconContainer, featureFlags) 130 qsCarrierGroupController = qsCarrierGroupControllerBuilder 131 .setQSCarrierGroup(statusBar.findViewById(R.id.carrier_group)) 132 .build() 133 updateVisibility() 134 updateConstraints() 135 } 136 137 private fun onShadeExpandedChanged() { 138 if (shadeExpanded) { 139 privacyIconsController.startListening() 140 } else { 141 privacyIconsController.stopListening() 142 } 143 updateVisibility() 144 updatePosition() 145 } 146 147 private fun onSplitShadeModeChanged() { 148 if (splitShadeMode) { 149 privacyIconsController.onParentVisible() 150 } else { 151 privacyIconsController.onParentInvisible() 152 } 153 updateVisibility() 154 updateConstraints() 155 } 156 157 private fun updateVisibility() { 158 val visibility = if (!splitShadeMode && !combinedHeaders) { 159 View.GONE 160 } else if (shadeExpanded) { 161 View.VISIBLE 162 } else { 163 View.INVISIBLE 164 } 165 if (statusBar.visibility != visibility) { 166 statusBar.visibility = visibility 167 visible = visibility == View.VISIBLE 168 } 169 } 170 171 private fun updateConstraints() { 172 if (!combinedHeaders) { 173 return 174 } 175 statusBar as MotionLayout 176 if (splitShadeMode) { 177 statusBar.setTransition(SPLIT_HEADER_TRANSITION_ID) 178 } else { 179 statusBar.setTransition(HEADER_TRANSITION_ID) 180 statusBar.transitionToStart() 181 updatePosition() 182 } 183 } 184 185 private fun updatePosition() { 186 if (statusBar is MotionLayout && !splitShadeMode && visible) { 187 statusBar.setProgress(qsExpandedFraction) 188 } 189 } 190 191 private fun updateListeners() { 192 qsCarrierGroupController.setListening(visible) 193 if (visible) { 194 updateSingleCarrier(qsCarrierGroupController.isSingleCarrier) 195 qsCarrierGroupController.setOnSingleCarrierChangedListener { updateSingleCarrier(it) } 196 statusBarIconController.addIconGroup(iconManager) 197 } else { 198 qsCarrierGroupController.setOnSingleCarrierChangedListener(null) 199 statusBarIconController.removeIconGroup(iconManager) 200 } 201 } 202 203 private fun updateSingleCarrier(singleCarrier: Boolean) { 204 if (singleCarrier) { 205 iconContainer.removeIgnoredSlots(carrierIconSlots) 206 } else { 207 iconContainer.addIgnoredSlots(carrierIconSlots) 208 } 209 } 210 } 211