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.Context
21 import android.content.Intent
22 import android.content.IntentFilter
23 import android.os.Handler
24 import android.os.Looper
25 import android.os.UserHandle
26 import android.util.Log
27 import com.android.systemui.SysuiTestableContext
28 import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
29 import com.android.systemui.dump.DumpManager
30 import com.android.systemui.settings.UserTracker
31 import java.lang.IllegalStateException
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 
66     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 
77     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 
88     override fun unregisterReceiver(receiver: BroadcastReceiver) {
89         receivers.removeIf { it.receiver == receiver }
90     }
91 
92     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     fun sendIntentToMatchingReceiversOnly(context: Context, intent: Intent) {
101         receivers.forEach {
102             if (
103                 it.filter.match(
104                     context.contentResolver,
105                     intent,
106                     /* resolve= */ false,
107                     /* logTag= */ "FakeBroadcastDispatcher",
108                 ) > 0
109             ) {
110                 it.receiver.onReceive(context, intent)
111             }
112         }
113     }
114 
115     val numReceiversRegistered: Int
116         get() = receivers.size
117 
118     fun cleanUpReceivers(testName: String) {
119         receivers.forEach {
120             val receiver = it.receiver
121             Log.i(testName, "Receiver not unregistered from dispatcher: $receiver")
122             if (shouldFailOnLeakedReceiver) {
123                 throw IllegalStateException("Receiver not unregistered from dispatcher: $receiver")
124             }
125         }
126         receivers.clear()
127     }
128 
129     private data class InternalReceiver(
130         val receiver: BroadcastReceiver,
131         val filter: IntentFilter,
132     )
133 }
134