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