• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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"