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