1 /*
<lambda>null2  * Copyright (C) 2019 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.permissioncontroller.permission.data
18 
19 import android.app.Application
20 import android.content.BroadcastReceiver
21 import android.content.Context
22 import android.content.Intent
23 import android.content.IntentFilter
24 import com.android.modules.utils.build.SdkLevel
25 import com.android.permissioncontroller.PermissionControllerApplication
26 import com.android.permissioncontroller.permission.data.v34.LightInstallSourceInfoLiveData
27 import com.android.permissioncontroller.permission.data.v34.SafetyLabelInfoLiveData
28 import com.android.permissioncontroller.permission.data.v35.PackagePermissionsExternalDeviceLiveData
29 import kotlinx.coroutines.Dispatchers.Main
30 import kotlinx.coroutines.GlobalScope
31 import kotlinx.coroutines.launch
32 
33 /** Listens for package additions, replacements, and removals, and notifies listeners. */
34 object PackageBroadcastReceiver : BroadcastReceiver() {
35 
36     private val app: Application = PermissionControllerApplication.get()
37     private val intentFilter =
38         IntentFilter(Intent.ACTION_PACKAGE_ADDED).apply {
39             addAction(Intent.ACTION_PACKAGE_REMOVED)
40             addAction(Intent.ACTION_PACKAGE_REPLACED)
41             addAction(Intent.ACTION_PACKAGE_CHANGED)
42             addDataScheme("package")
43         }
44 
45     /** Map<packageName, callbacks listenening to package> */
46     private val changeCallbacks = mutableMapOf<String, MutableSet<PackageBroadcastListener>>()
47     /** A list of listener IDs, which listen to all package additions, changes, and removals. */
48     private val allCallbacks = mutableSetOf<PackageBroadcastListener>()
49 
50     /** Add a callback which will be notified when the specified packaged is changed or removed. */
51     fun addChangeCallback(packageName: String, listener: PackageBroadcastListener) {
52         GlobalScope.launch(Main.immediate) {
53             val wasEmpty = hasNoListeners()
54 
55             changeCallbacks.getOrPut(packageName, { mutableSetOf() }).add(listener)
56 
57             if (wasEmpty) {
58                 app.applicationContext.registerReceiverForAllUsers(
59                     this@PackageBroadcastReceiver,
60                     intentFilter,
61                     null,
62                     null
63                 )
64             }
65         }
66     }
67 
68     /**
69      * Add a callback which will be notified any time a package is added, removed, or changed.
70      *
71      * @param listener the listener to be added
72      * @return returns the integer ID assigned to the
73      */
74     fun addAllCallback(listener: PackageBroadcastListener) {
75         GlobalScope.launch(Main.immediate) {
76             val wasEmpty = hasNoListeners()
77 
78             allCallbacks.add(listener)
79 
80             if (wasEmpty) {
81                 app.applicationContext.registerReceiverForAllUsers(
82                     this@PackageBroadcastReceiver,
83                     intentFilter,
84                     null,
85                     null
86                 )
87             }
88         }
89     }
90 
91     /**
92      * Removes a package add/remove/change callback.
93      *
94      * @param listener the listener we wish to remove
95      */
96     fun removeAllCallback(listener: PackageBroadcastListener) {
97         GlobalScope.launch(Main.immediate) {
98             val wasEmpty = hasNoListeners()
99 
100             if (allCallbacks.remove(listener) && hasNoListeners() && !wasEmpty) {
101                 app.applicationContext.unregisterReceiver(this@PackageBroadcastReceiver)
102             }
103         }
104     }
105 
106     /**
107      * Removes a change callback.
108      *
109      * @param packageName the package the listener is listening for
110      * @param listener the listener we wish to remove
111      */
112     fun removeChangeCallback(packageName: String?, listener: PackageBroadcastListener) {
113         GlobalScope.launch(Main.immediate) {
114             val wasEmpty = hasNoListeners()
115 
116             changeCallbacks[packageName]?.let { callbackSet ->
117                 callbackSet.remove(listener)
118                 if (callbackSet.isEmpty()) {
119                     changeCallbacks.remove(packageName)
120                 }
121                 if (hasNoListeners() && !wasEmpty) {
122                     app.applicationContext.unregisterReceiver(this@PackageBroadcastReceiver)
123                 }
124             }
125         }
126     }
127 
128     private fun getNumListeners(): Int {
129         var numListeners = allCallbacks.size
130         for ((_, changeCallbackSet) in changeCallbacks) {
131             numListeners += changeCallbackSet.size
132         }
133         return numListeners
134     }
135 
136     private fun hasNoListeners(): Boolean {
137         return getNumListeners() == 0
138     }
139 
140     /**
141      * Upon receiving a broadcast, rout it to the proper callbacks.
142      *
143      * @param context the context of the broadcast
144      * @param intent data about the broadcast which was sent
145      */
146     override fun onReceive(context: Context, intent: Intent) {
147         val packageName = intent.data?.schemeSpecificPart ?: return
148 
149         for (callback in allCallbacks.toList()) {
150             callback.onPackageUpdate(packageName)
151         }
152 
153         if (intent.action != Intent.ACTION_PACKAGE_ADDED) {
154             changeCallbacks[packageName]?.toList()?.let { callbacks ->
155                 for (callback in callbacks) {
156                     callback.onPackageUpdate(packageName)
157                 }
158             }
159         }
160 
161         if (intent.action == Intent.ACTION_PACKAGE_REMOVED) {
162             // Invalidate all livedatas associated with this package
163             LightPackageInfoLiveData.invalidateAllForPackage(packageName)
164             PermStateLiveData.invalidateAllForPackage(packageName)
165             PackagePermissionsLiveData.invalidateAllForPackage(packageName)
166             PackagePermissionsExternalDeviceLiveData.invalidateAllForPackage(packageName)
167             HibernationSettingStateLiveData.invalidateAllForPackage(packageName)
168             LightAppPermGroupLiveData.invalidateAllForPackage(packageName)
169             AppPermGroupUiInfoLiveData.invalidateAllForPackage(packageName)
170             if (SdkLevel.isAtLeastU()) {
171                 SafetyLabelInfoLiveData.invalidateAllForPackage(packageName)
172                 LightInstallSourceInfoLiveData.invalidateAllForPackage(packageName)
173             }
174         }
175     }
176 
177     /** A listener interface for objects desiring to be notified of package broadcasts. */
178     interface PackageBroadcastListener {
179         /**
180          * To be called when a specific package has been changed, or when any package has been
181          * installed.
182          *
183          * @param packageName the name of the package which was updated
184          */
185         fun onPackageUpdate(packageName: String)
186     }
187 }
188