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.events 18 19 import android.annotation.IntRange 20 import android.content.Context 21 import android.provider.DeviceConfig 22 import android.provider.DeviceConfig.NAMESPACE_PRIVACY 23 import com.android.systemui.R 24 import com.android.systemui.dagger.SysUISingleton 25 import com.android.systemui.flags.FeatureFlags 26 import com.android.systemui.flags.Flags 27 import com.android.systemui.privacy.PrivacyChipBuilder 28 import com.android.systemui.privacy.PrivacyItem 29 import com.android.systemui.privacy.PrivacyItemController 30 import com.android.systemui.statusbar.policy.BatteryController 31 import com.android.systemui.util.time.SystemClock 32 import javax.inject.Inject 33 34 /** 35 * Listens for system events (battery, privacy, connectivity) and allows listeners 36 * to show status bar animations when they happen 37 */ 38 @SysUISingleton 39 class SystemEventCoordinator @Inject constructor( 40 private val systemClock: SystemClock, 41 private val batteryController: BatteryController, 42 private val privacyController: PrivacyItemController, 43 private val context: Context, 44 private val featureFlags: FeatureFlags 45 ) { 46 private lateinit var scheduler: SystemStatusAnimationScheduler 47 startObservingnull48 fun startObserving() { 49 batteryController.addCallback(batteryStateListener) 50 privacyController.addCallback(privacyStateListener) 51 } 52 stopObservingnull53 fun stopObserving() { 54 batteryController.removeCallback(batteryStateListener) 55 privacyController.removeCallback(privacyStateListener) 56 } 57 attachSchedulernull58 fun attachScheduler(s: SystemStatusAnimationScheduler) { 59 this.scheduler = s 60 } 61 notifyPluggedInnull62 fun notifyPluggedIn(@IntRange(from = 0, to = 100) batteryLevel: Int) { 63 if (featureFlags.isEnabled(Flags.PLUG_IN_STATUS_BAR_CHIP)) { 64 scheduler.onStatusEvent(BatteryEvent(batteryLevel)) 65 } 66 } 67 notifyPrivacyItemsEmptynull68 fun notifyPrivacyItemsEmpty() { 69 scheduler.removePersistentDot() 70 } 71 notifyPrivacyItemsChangednull72 fun notifyPrivacyItemsChanged(showAnimation: Boolean = true) { 73 val event = PrivacyEvent(showAnimation) 74 event.privacyItems = privacyStateListener.currentPrivacyItems 75 event.contentDescription = run { 76 val items = PrivacyChipBuilder(context, event.privacyItems).joinTypes() 77 context.getString( 78 R.string.ongoing_privacy_chip_content_multiple_apps, items) 79 } 80 scheduler.onStatusEvent(event) 81 } 82 83 private val batteryStateListener = object : BatteryController.BatteryStateChangeCallback { 84 private var plugged = false 85 private var stateKnown = false onBatteryLevelChangednull86 override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) { 87 if (!stateKnown) { 88 stateKnown = true 89 plugged = pluggedIn 90 notifyListeners(level) 91 return 92 } 93 94 if (plugged != pluggedIn) { 95 plugged = pluggedIn 96 notifyListeners(level) 97 } 98 } 99 notifyListenersnull100 private fun notifyListeners(@IntRange(from = 0, to = 100) batteryLevel: Int) { 101 // We only care about the plugged in status 102 if (plugged) notifyPluggedIn(batteryLevel) 103 } 104 } 105 106 private val privacyStateListener = object : PrivacyItemController.Callback { 107 var currentPrivacyItems = listOf<PrivacyItem>() 108 var previousPrivacyItems = listOf<PrivacyItem>() 109 var timeLastEmpty = systemClock.elapsedRealtime() 110 onPrivacyItemsChangednull111 override fun onPrivacyItemsChanged(privacyItems: List<PrivacyItem>) { 112 if (uniqueItemsMatch(privacyItems, currentPrivacyItems)) { 113 return 114 } else if (privacyItems.isEmpty()) { 115 previousPrivacyItems = currentPrivacyItems 116 timeLastEmpty = systemClock.elapsedRealtime() 117 } 118 119 currentPrivacyItems = privacyItems 120 notifyListeners() 121 } 122 notifyListenersnull123 private fun notifyListeners() { 124 if (currentPrivacyItems.isEmpty()) { 125 notifyPrivacyItemsEmpty() 126 } else { 127 val showAnimation = isChipAnimationEnabled() && 128 (!uniqueItemsMatch(currentPrivacyItems, previousPrivacyItems) || 129 systemClock.elapsedRealtime() - timeLastEmpty >= DEBOUNCE_TIME) 130 notifyPrivacyItemsChanged(showAnimation) 131 } 132 } 133 134 // Return true if the lists contain the same permission groups, used by the same UIDs uniqueItemsMatchnull135 private fun uniqueItemsMatch(one: List<PrivacyItem>, two: List<PrivacyItem>): Boolean { 136 return one.map { it.application.uid to it.privacyType.permGroupName }.toSet() == 137 two.map { it.application.uid to it.privacyType.permGroupName }.toSet() 138 } 139 isChipAnimationEnablednull140 private fun isChipAnimationEnabled(): Boolean { 141 return DeviceConfig.getBoolean(NAMESPACE_PRIVACY, CHIP_ANIMATION_ENABLED, true) 142 } 143 } 144 } 145 146 private const val DEBOUNCE_TIME = 3000L 147 private const val CHIP_ANIMATION_ENABLED = "privacy_chip_animation_enabled" 148 private const val TAG = "SystemEventCoordinator"