1 package com.android.systemui.qs 2 3 import android.view.View 4 import com.android.internal.R 5 import com.android.internal.logging.UiEventLogger 6 import com.android.systemui.privacy.OngoingPrivacyChip 7 import com.android.systemui.privacy.PrivacyChipEvent 8 import com.android.systemui.privacy.PrivacyDialogController 9 import com.android.systemui.privacy.PrivacyItem 10 import com.android.systemui.privacy.PrivacyItemController 11 import com.android.systemui.privacy.logging.PrivacyLogger 12 import com.android.systemui.statusbar.phone.StatusIconContainer 13 import javax.inject.Inject 14 15 interface ChipVisibilityListener { 16 fun onChipVisibilityRefreshed(visible: Boolean) 17 } 18 19 /** 20 * Controls privacy icons/chip residing in QS header which show up when app is using camera, 21 * microphone or location. 22 * Manages their visibility depending on privacy signals coming from [PrivacyItemController]. 23 * 24 * Unlike typical controller extending [com.android.systemui.util.ViewController] this view doesn't 25 * observe its attachment state because depending on where it is used, it might be never detached. 26 * Instead, parent controller should use [onParentVisible] and [onParentInvisible] to "activate" or 27 * "deactivate" this controller. 28 */ 29 class HeaderPrivacyIconsController @Inject constructor( 30 private val privacyItemController: PrivacyItemController, 31 private val uiEventLogger: UiEventLogger, 32 private val privacyChip: OngoingPrivacyChip, 33 private val privacyDialogController: PrivacyDialogController, 34 private val privacyLogger: PrivacyLogger, 35 private val iconContainer: StatusIconContainer 36 ) { 37 38 var chipVisibilityListener: ChipVisibilityListener? = null 39 private var listening = false 40 private var micCameraIndicatorsEnabled = false 41 private var locationIndicatorsEnabled = false 42 private var privacyChipLogged = false 43 private val cameraSlot = privacyChip.resources.getString(R.string.status_bar_camera) 44 private val micSlot = privacyChip.resources.getString(R.string.status_bar_microphone) 45 private val locationSlot = privacyChip.resources.getString(R.string.status_bar_location) 46 47 private val picCallback: PrivacyItemController.Callback = 48 object : PrivacyItemController.Callback { 49 override fun onPrivacyItemsChanged(privacyItems: List<PrivacyItem>) { 50 privacyChip.privacyList = privacyItems 51 setChipVisibility(privacyItems.isNotEmpty()) 52 } 53 54 override fun onFlagMicCameraChanged(flag: Boolean) { 55 if (micCameraIndicatorsEnabled != flag) { 56 micCameraIndicatorsEnabled = flag 57 update() 58 } 59 } 60 61 override fun onFlagLocationChanged(flag: Boolean) { 62 if (locationIndicatorsEnabled != flag) { 63 locationIndicatorsEnabled = flag 64 update() 65 } 66 } 67 68 private fun update() { 69 updatePrivacyIconSlots() 70 setChipVisibility(privacyChip.privacyList.isNotEmpty()) 71 } 72 } 73 74 private fun getChipEnabled() = micCameraIndicatorsEnabled || locationIndicatorsEnabled 75 76 fun onParentVisible() { 77 privacyChip.setOnClickListener { 78 // If the privacy chip is visible, it means there were some indicators 79 uiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_CLICK) 80 privacyDialogController.showDialog(privacyChip.context) 81 } 82 setChipVisibility(privacyChip.visibility == View.VISIBLE) 83 micCameraIndicatorsEnabled = privacyItemController.micCameraAvailable 84 locationIndicatorsEnabled = privacyItemController.locationAvailable 85 86 // Ignore privacy icons because they show in the space above QQS 87 updatePrivacyIconSlots() 88 } 89 90 fun onParentInvisible() { 91 chipVisibilityListener = null 92 privacyChip.setOnClickListener(null) 93 } 94 95 fun startListening() { 96 listening = true 97 // Get the most up to date info 98 micCameraIndicatorsEnabled = privacyItemController.micCameraAvailable 99 locationIndicatorsEnabled = privacyItemController.locationAvailable 100 privacyItemController.addCallback(picCallback) 101 } 102 103 fun stopListening() { 104 listening = false 105 privacyItemController.removeCallback(picCallback) 106 privacyChipLogged = false 107 } 108 109 private fun setChipVisibility(visible: Boolean) { 110 if (visible && getChipEnabled()) { 111 privacyLogger.logChipVisible(true) 112 // Makes sure that the chip is logged as viewed at most once each time QS is opened 113 // mListening makes sure that the callback didn't return after the user closed QS 114 if (!privacyChipLogged && listening) { 115 privacyChipLogged = true 116 uiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_VIEW) 117 } 118 } else { 119 privacyLogger.logChipVisible(false) 120 } 121 122 privacyChip.visibility = if (visible) View.VISIBLE else View.GONE 123 chipVisibilityListener?.onChipVisibilityRefreshed(visible) 124 } 125 126 private fun updatePrivacyIconSlots() { 127 if (getChipEnabled()) { 128 if (micCameraIndicatorsEnabled) { 129 iconContainer.addIgnoredSlot(cameraSlot) 130 iconContainer.addIgnoredSlot(micSlot) 131 } else { 132 iconContainer.removeIgnoredSlot(cameraSlot) 133 iconContainer.removeIgnoredSlot(micSlot) 134 } 135 if (locationIndicatorsEnabled) { 136 iconContainer.addIgnoredSlot(locationSlot) 137 } else { 138 iconContainer.removeIgnoredSlot(locationSlot) 139 } 140 } else { 141 iconContainer.removeIgnoredSlot(cameraSlot) 142 iconContainer.removeIgnoredSlot(micSlot) 143 iconContainer.removeIgnoredSlot(locationSlot) 144 } 145 } 146 }