1 /*
2  * Copyright (C) 2020 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.broadcast
18 
19 import android.content.BroadcastReceiver
20 import android.content.BroadcastReceiver.PendingResult
21 import android.content.Context
22 import android.content.Intent
23 import android.content.IntentFilter
24 import android.os.Handler
25 import android.os.Looper
26 import android.os.UserHandle
27 import android.util.Log
28 import com.android.systemui.SysuiTestableContext
29 import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
30 import com.android.systemui.dump.DumpManager
31 import com.android.systemui.settings.UserTracker
32 import java.util.concurrent.ConcurrentHashMap
33 import java.util.concurrent.Executor
34 
35 /**
36  * A fake instance of [BroadcastDispatcher] for tests.
37  *
38  * Important: The *real* broadcast dispatcher will only send intents to receivers if the intent
39  * matches the [IntentFilter] that the [BroadcastReceiver] was registered with. This fake class
40  * exposes [sendIntentToMatchingReceiversOnly] to get the same matching behavior as the real
41  * broadcast dispatcher.
42  */
43 class FakeBroadcastDispatcher(
44     context: SysuiTestableContext,
45     mainExecutor: Executor,
46     broadcastRunningLooper: Looper,
47     broadcastRunningExecutor: Executor,
48     dumpManager: DumpManager,
49     logger: BroadcastDispatcherLogger,
50     userTracker: UserTracker,
51     private val shouldFailOnLeakedReceiver: Boolean
52 ) :
53     BroadcastDispatcher(
54         context,
55         mainExecutor,
56         broadcastRunningLooper,
57         broadcastRunningExecutor,
58         dumpManager,
59         logger,
60         userTracker,
61         PendingRemovalStore(logger)
62     ) {
63 
64     private val receivers: MutableSet<InternalReceiver> = ConcurrentHashMap.newKeySet()
65 
registerReceiverWithHandlernull66     override fun registerReceiverWithHandler(
67         receiver: BroadcastReceiver,
68         filter: IntentFilter,
69         handler: Handler,
70         user: UserHandle,
71         @Context.RegisterReceiverFlags flags: Int,
72         permission: String?
73     ) {
74         receivers.add(InternalReceiver(receiver, filter))
75     }
76 
registerReceivernull77     override fun registerReceiver(
78         receiver: BroadcastReceiver,
79         filter: IntentFilter,
80         executor: Executor?,
81         user: UserHandle?,
82         @Context.RegisterReceiverFlags flags: Int,
83         permission: String?
84     ) {
85         receivers.add(InternalReceiver(receiver, filter))
86     }
87 
unregisterReceivernull88     override fun unregisterReceiver(receiver: BroadcastReceiver) {
89         receivers.removeIf { it.receiver == receiver }
90     }
91 
unregisterReceiverForUsernull92     override fun unregisterReceiverForUser(receiver: BroadcastReceiver, user: UserHandle) {
93         receivers.removeIf { it.receiver == receiver }
94     }
95 
96     /**
97      * Sends the given [intent] to *only* the receivers that were registered with an [IntentFilter]
98      * that matches the intent.
99      *
100      * A non-null [pendingResult] can be used to pass the sending user.
101      */
sendIntentToMatchingReceiversOnlynull102     fun sendIntentToMatchingReceiversOnly(
103         context: Context,
104         intent: Intent,
105         pendingResult: PendingResult? = null
106     ) {
107         receivers.forEach {
108             if (
109                 it.filter.match(
110                     context.contentResolver,
111                     intent,
112                     /* resolve= */ false,
113                     /* logTag= */ "FakeBroadcastDispatcher",
114                 ) > 0
115             ) {
116                 if (pendingResult != null) {
117                     it.receiver.pendingResult = pendingResult
118                 }
119                 it.receiver.onReceive(context, intent)
120             }
121         }
122     }
123 
124     val numReceiversRegistered: Int
125         get() = receivers.size
126 
cleanUpReceiversnull127     fun cleanUpReceivers(testName: String) {
128         receivers.forEach {
129             val receiver = it.receiver
130             Log.i(testName, "Receiver not unregistered from dispatcher: $receiver")
131             if (shouldFailOnLeakedReceiver) {
132                 throw IllegalStateException("Receiver not unregistered from dispatcher: $receiver")
133             }
134         }
135         receivers.clear()
136     }
137 
138     private data class InternalReceiver(
139         val receiver: BroadcastReceiver,
140         val filter: IntentFilter,
141     )
142 
143     companion object {
fakePendingResultForUsernull144         fun fakePendingResultForUser(userId: Int) =
145             PendingResult(
146                 /* resultCode = */ 0,
147                 /* resultData = */ "",
148                 /* resultExtras = */ null,
149                 /* type = */ PendingResult.TYPE_REGISTERED,
150                 /* ordered = */ false,
151                 /* sticky = */ false,
152                 /* token = */ null,
153                 userId,
154                 /* flags = */ 0,
155             )
156     }
157 }
158