1 /*
2  * Copyright 2022 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 android.bluetooth;
18 
19 import android.annotation.NonNull;
20 import android.os.Binder;
21 import android.os.RemoteException;
22 import android.util.Log;
23 
24 import java.util.HashMap;
25 import java.util.Map;
26 import java.util.concurrent.Executor;
27 
28 /** @hide */
29 public class BluetoothLeBroadcastAssistantCallback
30         extends IBluetoothLeBroadcastAssistantCallback.Stub {
31     private static final String TAG = BluetoothLeBroadcastAssistantCallback.class.getSimpleName();
32     private boolean mIsRegistered = false;
33     private final Map<BluetoothLeBroadcastAssistant.Callback, Executor> mCallbackMap =
34             new HashMap<>();
35     IBluetoothLeBroadcastAssistant mAdapter;
36 
BluetoothLeBroadcastAssistantCallback(IBluetoothLeBroadcastAssistant adapter)37     public BluetoothLeBroadcastAssistantCallback(IBluetoothLeBroadcastAssistant adapter) {
38         mAdapter = adapter;
39     }
40 
41     /**
42      * @hide
43      * @param executor an {@link Executor} to execute given callback
44      * @param callback user implementation of the {@link BluetoothLeBroadcastAssistant#Callback}
45      * @throws IllegalArgumentException if the same <var>callback<var> is already registered.
46      */
register( @onNull Executor executor, @NonNull BluetoothLeBroadcastAssistant.Callback callback)47     public void register(
48             @NonNull Executor executor, @NonNull BluetoothLeBroadcastAssistant.Callback callback) {
49         synchronized (this) {
50             if (mCallbackMap.containsKey(callback)) {
51                 throw new IllegalArgumentException("callback is already registered");
52             }
53             mCallbackMap.put(callback, executor);
54 
55             if (!mIsRegistered) {
56                 try {
57                     mAdapter.registerCallback(this);
58                     mIsRegistered = true;
59                 } catch (RemoteException e) {
60                     Log.w(TAG, "Failed to register broadcast assistant callback");
61                     Log.e(TAG, Log.getStackTraceString(new Throwable()));
62                 }
63             }
64         }
65     }
66 
67     /**
68      * @hide
69      * @param callback user implementation of the {@link BluetoothLeBroadcastAssistant#Callback}
70      * @throws IllegalArgumentException if <var>callback</var> was not registered before
71      */
unregister(@onNull BluetoothLeBroadcastAssistant.Callback callback)72     public void unregister(@NonNull BluetoothLeBroadcastAssistant.Callback callback) {
73         synchronized (this) {
74             if (!mCallbackMap.containsKey(callback)) {
75                 throw new IllegalArgumentException("callback was not registered before");
76             }
77             mCallbackMap.remove(callback);
78             if (mCallbackMap.isEmpty() && mIsRegistered) {
79                 try {
80                     mAdapter.unregisterCallback(this);
81                     mIsRegistered = false;
82                 } catch (RemoteException e) {
83                     Log.w(TAG, "Failed to unregister callback with service");
84                     Log.e(TAG, Log.getStackTraceString(new Throwable()));
85                 }
86             }
87         }
88     }
89 
90     /**
91      * Check if at least one callback is registered from this App
92      *
93      * @return true if at least one callback is registered
94      * @hide
95      */
isAtLeastOneCallbackRegistered()96     public boolean isAtLeastOneCallbackRegistered() {
97         synchronized (this) {
98             return !mCallbackMap.isEmpty();
99         }
100     }
101 
102     @Override
onSearchStarted(int reason)103     public void onSearchStarted(int reason) {
104         synchronized (this) {
105             for (BluetoothLeBroadcastAssistant.Callback cb : mCallbackMap.keySet()) {
106                 Executor executor = mCallbackMap.get(cb);
107                 final long identity = Binder.clearCallingIdentity();
108                 try {
109                     executor.execute(() -> cb.onSearchStarted(reason));
110                 } finally {
111                     Binder.restoreCallingIdentity(identity);
112                 }
113             }
114         }
115     }
116 
117     @Override
onSearchStartFailed(int reason)118     public void onSearchStartFailed(int reason) {
119         synchronized (this) {
120             for (BluetoothLeBroadcastAssistant.Callback cb : mCallbackMap.keySet()) {
121                 Executor executor = mCallbackMap.get(cb);
122                 final long identity = Binder.clearCallingIdentity();
123                 try {
124                     executor.execute(() -> cb.onSearchStartFailed(reason));
125                 } finally {
126                     Binder.restoreCallingIdentity(identity);
127                 }
128             }
129         }
130     }
131 
132     @Override
onSearchStopped(int reason)133     public void onSearchStopped(int reason) {
134         synchronized (this) {
135             for (BluetoothLeBroadcastAssistant.Callback cb : mCallbackMap.keySet()) {
136                 Executor executor = mCallbackMap.get(cb);
137                 final long identity = Binder.clearCallingIdentity();
138                 try {
139                     executor.execute(() -> cb.onSearchStopped(reason));
140                 } finally {
141                     Binder.restoreCallingIdentity(identity);
142                 }
143             }
144         }
145     }
146 
147     @Override
onSearchStopFailed(int reason)148     public void onSearchStopFailed(int reason) {
149         synchronized (this) {
150             for (BluetoothLeBroadcastAssistant.Callback cb : mCallbackMap.keySet()) {
151                 Executor executor = mCallbackMap.get(cb);
152                 final long identity = Binder.clearCallingIdentity();
153                 try {
154                     executor.execute(() -> cb.onSearchStopFailed(reason));
155                 } finally {
156                     Binder.restoreCallingIdentity(identity);
157                 }
158             }
159         }
160     }
161 
162     @Override
onSourceFound(BluetoothLeBroadcastMetadata source)163     public void onSourceFound(BluetoothLeBroadcastMetadata source) {
164         synchronized (this) {
165             for (BluetoothLeBroadcastAssistant.Callback cb : mCallbackMap.keySet()) {
166                 Executor executor = mCallbackMap.get(cb);
167                 final long identity = Binder.clearCallingIdentity();
168                 try {
169                     executor.execute(() -> cb.onSourceFound(source));
170                 } finally {
171                     Binder.restoreCallingIdentity(identity);
172                 }
173             }
174         }
175     }
176 
177     @Override
onSourceAdded(BluetoothDevice sink, int sourceId, int reason)178     public void onSourceAdded(BluetoothDevice sink, int sourceId, int reason) {
179         synchronized (this) {
180             for (BluetoothLeBroadcastAssistant.Callback cb : mCallbackMap.keySet()) {
181                 Executor executor = mCallbackMap.get(cb);
182                 final long identity = Binder.clearCallingIdentity();
183                 try {
184                     executor.execute(() -> cb.onSourceAdded(sink, sourceId, reason));
185                 } finally {
186                     Binder.restoreCallingIdentity(identity);
187                 }
188             }
189         }
190     }
191 
192     @Override
onSourceAddFailed( BluetoothDevice sink, BluetoothLeBroadcastMetadata source, int reason)193     public void onSourceAddFailed(
194             BluetoothDevice sink, BluetoothLeBroadcastMetadata source, int reason) {
195         synchronized (this) {
196             for (BluetoothLeBroadcastAssistant.Callback cb : mCallbackMap.keySet()) {
197                 Executor executor = mCallbackMap.get(cb);
198                 final long identity = Binder.clearCallingIdentity();
199                 try {
200                     executor.execute(() -> cb.onSourceAddFailed(sink, source, reason));
201                 } finally {
202                     Binder.restoreCallingIdentity(identity);
203                 }
204             }
205         }
206     }
207 
208     @Override
onSourceModified(BluetoothDevice sink, int sourceId, int reason)209     public void onSourceModified(BluetoothDevice sink, int sourceId, int reason) {
210         synchronized (this) {
211             for (BluetoothLeBroadcastAssistant.Callback cb : mCallbackMap.keySet()) {
212                 Executor executor = mCallbackMap.get(cb);
213                 final long identity = Binder.clearCallingIdentity();
214                 try {
215                     executor.execute(() -> cb.onSourceModified(sink, sourceId, reason));
216                 } finally {
217                     Binder.restoreCallingIdentity(identity);
218                 }
219             }
220         }
221     }
222 
223     @Override
onSourceModifyFailed(BluetoothDevice sink, int sourceId, int reason)224     public void onSourceModifyFailed(BluetoothDevice sink, int sourceId, int reason) {
225         synchronized (this) {
226             for (BluetoothLeBroadcastAssistant.Callback cb : mCallbackMap.keySet()) {
227                 Executor executor = mCallbackMap.get(cb);
228                 final long identity = Binder.clearCallingIdentity();
229                 try {
230                     executor.execute(() -> cb.onSourceModifyFailed(sink, sourceId, reason));
231                 } finally {
232                     Binder.restoreCallingIdentity(identity);
233                 }
234             }
235         }
236     }
237 
238     @Override
onSourceRemoved(BluetoothDevice sink, int sourceId, int reason)239     public void onSourceRemoved(BluetoothDevice sink, int sourceId, int reason) {
240         synchronized (this) {
241             for (BluetoothLeBroadcastAssistant.Callback cb : mCallbackMap.keySet()) {
242                 Executor executor = mCallbackMap.get(cb);
243                 final long identity = Binder.clearCallingIdentity();
244                 try {
245                     executor.execute(() -> cb.onSourceRemoved(sink, sourceId, reason));
246                 } finally {
247                     Binder.restoreCallingIdentity(identity);
248                 }
249             }
250         }
251     }
252 
253     @Override
onSourceRemoveFailed(BluetoothDevice sink, int sourceId, int reason)254     public void onSourceRemoveFailed(BluetoothDevice sink, int sourceId, int reason) {
255         synchronized (this) {
256             for (BluetoothLeBroadcastAssistant.Callback cb : mCallbackMap.keySet()) {
257                 Executor executor = mCallbackMap.get(cb);
258                 final long identity = Binder.clearCallingIdentity();
259                 try {
260                     executor.execute(() -> cb.onSourceRemoveFailed(sink, sourceId, reason));
261                 } finally {
262                     Binder.restoreCallingIdentity(identity);
263                 }
264             }
265         }
266     }
267 
268     @Override
onReceiveStateChanged( BluetoothDevice sink, int sourceId, BluetoothLeBroadcastReceiveState state)269     public void onReceiveStateChanged(
270             BluetoothDevice sink, int sourceId, BluetoothLeBroadcastReceiveState state) {
271         synchronized (this) {
272             for (BluetoothLeBroadcastAssistant.Callback cb : mCallbackMap.keySet()) {
273                 Executor executor = mCallbackMap.get(cb);
274                 final long identity = Binder.clearCallingIdentity();
275                 try {
276                     executor.execute(() -> cb.onReceiveStateChanged(sink, sourceId, state));
277                 } finally {
278                     Binder.restoreCallingIdentity(identity);
279                 }
280             }
281         }
282     }
283 
284     @Override
onSourceLost(int broadcastId)285     public void onSourceLost(int broadcastId) {
286         synchronized (this) {
287             for (BluetoothLeBroadcastAssistant.Callback cb : mCallbackMap.keySet()) {
288                 Executor executor = mCallbackMap.get(cb);
289                 final long identity = Binder.clearCallingIdentity();
290                 try {
291                     executor.execute(() -> cb.onSourceLost(broadcastId));
292                 } finally {
293                     Binder.restoreCallingIdentity(identity);
294                 }
295             }
296         }
297     }
298 }
299