1 /*
<lambda>null2  * Copyright (C) 2024 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.wallpaper.picker.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 com.android.systemui.dagger.qualifiers.Main
26 import com.android.wallpaper.picker.di.modules.ConcurrencyModule.*
27 import java.util.concurrent.Executor
28 import javax.inject.Inject
29 import javax.inject.Singleton
30 import kotlinx.coroutines.channels.awaitClose
31 import kotlinx.coroutines.flow.Flow
32 import kotlinx.coroutines.flow.callbackFlow
33 
34 /**
35  * WallpaperPicker master Broadcast Dispatcher.
36  *
37  * This class allows [BroadcastReceiver] to register and centralizes registrations to [Context] from
38  * WallpaperPicker. That way the number of calls to [BroadcastReceiver.onReceive] can be reduced for
39  * a given broadcast.
40  *
41  * Use only for IntentFilters with actions and optionally categories. It does not support schemes,
42  * data types, data authorities or priority different than 0.
43  *
44  * Cannot be used for getting sticky broadcasts (either as return of registering or as re-delivery).
45  */
46 @Singleton
47 open class BroadcastDispatcher
48 @Inject
49 constructor(
50     private val context: Context,
51     @Main private val mainExecutor: Executor,
52     @BroadcastRunning private val broadcastLooper: Looper,
53     @BroadcastRunning private val broadcastExecutor: Executor,
54 ) {
55     /**
56      * Register a receiver for broadcast with the dispatcher
57      *
58      * @param receiver A receiver to dispatch the [Intent]
59      * @param filter A filter to determine what broadcasts should be dispatched to this receiver. It
60      *   will only take into account actions and categories for filtering. It must have at least one
61      *   action.
62      * @param executor An executor to dispatch [BroadcastReceiver.onReceive].
63      * @param flags Flags to use when registering the receiver. [Context.RECEIVER_EXPORTED] by
64      *   default.
65      * @param permission to enforce security on who can send broadcasts to the receiver.
66      * @throws IllegalArgumentException if the filter has other constraints that are not actions or
67      *   categories or the filter has no actions.
68      */
69     @JvmOverloads
70     open fun registerReceiver(
71         receiver: BroadcastReceiver,
72         filter: IntentFilter,
73         executor: Executor = mainExecutor,
74         @Context.RegisterReceiverFlags flags: Int = Context.RECEIVER_EXPORTED,
75         permission: String? = null
76     ) {
77         checkFilter(filter)
78         context.registerReceiver(receiver, filter, permission, Handler(broadcastLooper), flags)
79     }
80 
81     /**
82      * Returns a [Flow] that, when collected, emits a new value whenever a broadcast matching
83      * [filter] is received. The value will be computed from the intent and the registered receiver
84      * using [map].
85      *
86      * @see registerReceiver
87      */
88     @JvmOverloads
89     fun <T> broadcastFlow(
90         filter: IntentFilter,
91         @Context.RegisterReceiverFlags flags: Int = Context.RECEIVER_EXPORTED,
92         permission: String? = null,
93         map: (Intent, BroadcastReceiver) -> T,
94     ): Flow<T> = callbackFlow {
95         val receiver =
96             object : BroadcastReceiver() {
97                 override fun onReceive(context: Context, intent: Intent) {
98                     trySend(map(intent, this))
99                 }
100             }
101 
102         registerReceiver(
103             receiver,
104             filter,
105             broadcastExecutor,
106             flags,
107             permission,
108         )
109 
110         awaitClose { unregisterReceiver(receiver) }
111     }
112 
113     /**
114      * Returns a [Flow] that, when collected, emits `Unit` whenever a broadcast matching [filter] is
115      * received.
116      *
117      * @see registerReceiver
118      */
119     @JvmOverloads
120     fun broadcastFlow(
121         filter: IntentFilter,
122         @Context.RegisterReceiverFlags flags: Int = Context.RECEIVER_EXPORTED,
123         permission: String? = null,
124     ): Flow<Unit> = broadcastFlow(filter, flags, permission) { _, _ -> Unit }
125 
126     private fun checkFilter(filter: IntentFilter) {
127         buildString {
128                 if (filter.countActions() == 0) {
129                     append("Filter must contain at least one action. ")
130                 }
131                 if (filter.countDataAuthorities() != 0) {
132                     append("Filter cannot contain DataAuthorities. ")
133                 }
134                 if (filter.countDataPaths() != 0) {
135                     append("Filter cannot contain DataPaths. ")
136                 }
137                 if (filter.countDataSchemes() != 0) {
138                     append("Filter cannot contain DataSchemes. ")
139                 }
140                 if (filter.countDataTypes() != 0) {
141                     append("Filter cannot contain DataTypes. ")
142                 }
143                 if (filter.priority != 0) {
144                     append("Filter cannot modify priority. ")
145                 }
146             }
147             .let {
148                 if (it.isNotEmpty()) {
149                     throw IllegalArgumentException(it)
150                 }
151             }
152     }
153 
154     /**
155      * Unregister receiver for the current user.
156      *
157      * @param receiver The receiver to unregister.
158      */
159     open fun unregisterReceiver(receiver: BroadcastReceiver) {
160         context.unregisterReceiver(receiver)
161     }
162 }
163