Direct-BT v3.3.0-1-gc2d430c
Direct-BT - Direct Bluetooth Programming.
DBTManager.java
Go to the documentation of this file.
1/**
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright (c) 2020 Gothel Software e.K.
4 * Copyright (c) 2020 ZAFENA AB
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining
7 * a copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sublicense, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 */
25
26package jau.direct_bt;
27
28import java.security.PrivilegedAction;
29import java.util.ArrayList;
30import java.util.Iterator;
31import java.util.List;
32import java.util.concurrent.CopyOnWriteArrayList;
33import java.util.function.Consumer;
34import java.util.function.Predicate;
35
36import org.direct_bt.BTAdapter;
37import org.direct_bt.BTException;
38import org.direct_bt.BTFactory;
39import org.direct_bt.BTManager;
40
41public class DBTManager implements BTManager
42{
43 protected static final boolean DEBUG = BTFactory.DEBUG;
44 protected static final boolean VERBOSE = BTFactory.VERBOSE;
45
46 private static volatile boolean isJVMShuttingDown = false;
47 private static final List<Runnable> userShutdownHooks = new ArrayList<Runnable>();
48
49 static {
50 BTFactory.doPrivileged(new PrivilegedAction<Object>() {
51 @Override
52 public Object run() {
53 Runtime.getRuntime().addShutdownHook(
54 new Thread(null, new Runnable() {
55 @Override
56 public void run() {
57 DBTManager.shutdownImpl(true);
58 } }, "DBTManager_ShutdownHook" ) ) ;
59 if(DEBUG) {
60 System.err.println("DBTManager.init(): JVM ShutdownHook installed, on thread "+Thread.currentThread().getName());
61 }
62 return null;
63 } } ) ;
64 }
65
66 private static synchronized void shutdownImpl(final boolean _isJVMShuttingDown) {
67 isJVMShuttingDown = _isJVMShuttingDown;
68 if(DEBUG) {
69 System.err.println("DBTManager.shutdown() START: JVM ShutdownHook "+isJVMShuttingDown+", on thread "+Thread.currentThread().getName());
70 }
71 synchronized(userShutdownHooks) {
72 final int cshCount = userShutdownHooks.size();
73 for(int i=0; i < cshCount; i++) {
74 try {
75 if( DEBUG ) {
76 System.err.println("DBTManager.shutdown - userShutdownHook #"+(i+1)+"/"+cshCount);
77 }
78 userShutdownHooks.get(i).run();
79 } catch(final Throwable t) {
80 System.err.println("DBTManager.shutdown: Caught "+t.getClass().getName()+" during userShutdownHook #"+(i+1)+"/"+cshCount);
81 if( DEBUG ) {
82 t.printStackTrace();
83 }
84 }
85 }
86 userShutdownHooks.clear();
87 }
88 if(DEBUG) {
89 System.err.println("DBTManager.shutdown(): Post userShutdownHook");
90 }
91
92 try {
93 final BTManager mgmt = getManager();
94 mgmt.shutdown();
95 } catch(final Throwable t) {
96 System.err.println("DBTManager.shutdown: Caught "+t.getClass().getName()+" during DBTManager.shutdown()");
97 if( DEBUG ) {
98 t.printStackTrace();
99 }
100 }
101
102 if(DEBUG) {
103 System.err.println(Thread.currentThread().getName()+" - DBTManager.shutdown() END JVM Shutdown "+isJVMShuttingDown);
104 }
105 }
106
107 /** Returns true if the JVM is shutting down, otherwise false. */
108 public static final boolean isJVMShuttingDown() { return isJVMShuttingDown; }
109
110 /**
111 * Add a shutdown hook to be performed at JVM shutdown before shutting down DBTManager instance.
112 *
113 * @param head if true add runnable at the start, otherwise at the end
114 * @param runnable runnable to be added.
115 */
116 public static final void addShutdownHook(final boolean head, final Runnable runnable) {
117 synchronized( userShutdownHooks ) {
118 if( !userShutdownHooks.contains( runnable ) ) {
119 if( head ) {
120 userShutdownHooks.add(0, runnable);
121 } else {
122 userShutdownHooks.add( runnable );
123 }
124 }
125 }
126 }
127
128 private long nativeInstance;
129 private final List<BTAdapter> adapters = new CopyOnWriteArrayList<BTAdapter>();
130 private final List<ChangedAdapterSetListener> changedAdapterSetListenerList = new CopyOnWriteArrayList<ChangedAdapterSetListener>();
131
132 @Override
133 public final List<BTAdapter> getAdapters() { return new ArrayList<BTAdapter>(adapters); }
134
135 @Override
136 public final BTAdapter getAdapter(final int dev_id) {
137 for(final Iterator<BTAdapter> iter = adapters.iterator(); iter.hasNext(); ) {
138 final BTAdapter a = iter.next();
139 if( dev_id == a.getDevID() ) {
140 return a;
141 }
142 }
143 return null;
144 }
145
146 @Override
147 public final boolean setDefaultAdapter(final BTAdapter adapter) {
148 return false;
149 }
150
151 @Override
153 for(final Iterator<BTAdapter> iter = adapters.iterator(); iter.hasNext(); ) {
154 final BTAdapter a = iter.next();
155 if( a.isPowered() ) {
156 return a;
157 }
158 }
159 return null;
160 }
161
162 @Override
164 changedAdapterSetListenerList.add(l);
165
166 adapters.forEach(new Consumer<BTAdapter>() {
167 @Override
168 public void accept(final BTAdapter adapter) {
169 l.adapterAdded(adapter);
170 }
171 });
172 }
173
174 @Override
176 // Using removeIf allows performing test on all object and remove within one write-lock cycle
177 final int count[] = { 0 };
178 changedAdapterSetListenerList.removeIf(new Predicate<ChangedAdapterSetListener>() {
179 @Override
180 public boolean test(final ChangedAdapterSetListener t) {
181 if( t.equals(l) ) {
182 count[0]++;
183 return true;
184 }
185 return false;
186 }
187 });
188 return count[0];
189 }
190
191 @Override
193 final int count = changedAdapterSetListenerList.size();
194 changedAdapterSetListenerList.clear();
195 return count;
196 }
197
198 private native List<BTAdapter> getAdapterListImpl();
199 private native BTAdapter getAdapterImpl(int dev_id);
200
201 /**
202 * Removal entry for DBTAdapter.close()
203 * @param adapter ref to the close'ed adapter
204 * @return true if contained and removed, otherwise false
205 */
206 /* pp */ final boolean removeAdapter(final DBTAdapter adapter) {
207 if( adapters.remove(adapter) ) {
208 if( DEBUG ) {
209 System.err.println("DBTManager.removeAdapter: Removed "+adapter);
210 }
211 return true;
212 } else {
213 if( DEBUG ) {
214 System.err.println("DBTManager.removeAdapter: Not found "+adapter);
215 }
216 return false;
217 }
218 }
219
220 /** callback from native adapter remove */
221 /* pp */ final void removeAdapterCB(final int dev_id, final int opc_reason) {
222 final BTAdapter[] removed = { null };
223 final int count[] = { 0 };
224 adapters.removeIf(new Predicate<BTAdapter>() {
225 @Override
226 public boolean test(final BTAdapter a) {
227 if( 0 == count[0] && dev_id == a.getDevID() ) {
228 removed[0] = a;
229 count[0]++;
230 return true;
231 } else {
232 return false;
233 }
234 }
235 });
236 if( null != removed[0] ) {
237 if( DEBUG ) {
238 System.err.println("DBTManager.removeAdapterCB[dev_id "+dev_id+", opc 0x"+Integer.toHexString(opc_reason)+
239 "]: removing "+removed[0].toString());
240 }
241 if( 0x0005 == opc_reason ) {
242 // MgmtEvent::Opcode::INDEX_REMOVED = 0x0005
243 changedAdapterSetListenerList.forEach(new Consumer<ChangedAdapterSetListener>() {
244 @Override
245 public void accept(final ChangedAdapterSetListener t) {
246 t.adapterRemoved( removed[0] );
247 } } );
248 }
249 removed[0] = null; // DBTAdapter::close() issued by DBTManager after all callbacks
250 }
251 if( DEBUG ) {
252 System.err.println("DBTManager.removeAdapterCB[dev_id "+dev_id+", opc 0x"+Integer.toHexString(opc_reason)+
253 "]: removed "+count[0]+" adapter, size "+adapters.size());
254 }
255 }
256 /** callback from native adapter add or POWERED on */
257 private final void updatedAdapterCB(final int dev_id, final int opc_reason) {
258 final BTAdapter preInstance = getAdapter(dev_id);
259 if( null != preInstance ) {
260 if( DEBUG ) {
261 System.err.println("DBTManager.updatedAdapterCB[dev_id "+dev_id+", opc 0x"+Integer.toHexString(opc_reason)+
262 "]: existing "+preInstance.toString()+", size "+adapters.size());
263 }
264 return;
265 }
266 final BTAdapter newInstance = getAdapterImpl(dev_id);
267 if( null == newInstance ) {
268 if( DEBUG ) {
269 System.err.println("DBTManager.updatedAdapterCB[dev_id "+dev_id+", opc 0x"+Integer.toHexString(opc_reason)+
270 "]: Adapter not found, size "+adapters.size());
271 }
272 return;
273 }
274 final boolean added = adapters.add(newInstance);
275 if( DEBUG ) {
276 System.err.println("DBTManager.updatedAdapterCB[dev_id "+dev_id+", opc 0x"+Integer.toHexString(opc_reason)+
277 "]: added "+added+": new "+newInstance.toString()+", size "+adapters.size());
278 }
279 if( added && 0x0004 == opc_reason ) {
280 // MgmtEvent::Opcode::INDEX_ADDED = 0x0004
281 changedAdapterSetListenerList.forEach(new Consumer<ChangedAdapterSetListener>() {
282 @Override
283 public void accept(final ChangedAdapterSetListener t) {
284 t.adapterAdded(newInstance);
285 } } );
286 }
287 }
288
289 private native long ctorImpl() throws BTException;
290 private native void dtorImpl(long nativeInstance);
291 private DBTManager()
292 {
293 nativeInstance = ctorImpl();
294 try {
295 adapters.addAll(getAdapterListImpl());
296 } catch (final BTException be) {
297 be.printStackTrace();
298 }
299 }
300
301 /** Returns an instance of BluetoothManager, to be used instead of constructor.
302 * @return An initialized BluetoothManager instance.
303 */
304 public static final BTManager getManager() throws RuntimeException, BTException {
305 return LazySingletonHolder.singleton;
306 }
307 /** Initialize-On-Demand Holder Class, similar to C++11's "Magic Statics". */
308 private static class LazySingletonHolder {
309 private static final DBTManager singleton = new DBTManager();
310 }
311
312 @Override
313 protected final void finalize() {
314 shutdown();
315 }
316
317 @Override
318 public final void shutdown() {
319 for(final Iterator<BTAdapter> ia= adapters.iterator(); ia.hasNext(); ) {
320 final DBTAdapter a = (DBTAdapter)ia.next();
321 a.close();
322 }
323 adapters.clear();
324
325 final long handle;
326 synchronized( this ) {
327 handle = nativeInstance;
328 nativeInstance = 0;
329 }
330 if( 0 != handle ) {
331 dtorImpl(handle);
332 }
333 }
334}
final void close()
Release the native memory associated with this object The object should not be used following a call ...
final List< BTAdapter > getAdapters()
Returns a list of BluetoothAdapters available in the system.
final void shutdown()
Release the native memory associated with this object and all related Bluetooth resources.
static final boolean DEBUG
Definition: DBTManager.java:43
int removeAllChangedAdapterSetListener()
Remove all added ChangedAdapterSetListener entries from this manager.
final int removeChangedAdapterSetListener(final ChangedAdapterSetListener l)
Remove the given ChangedAdapterSetListener from this manager.
final BTAdapter getAdapter(final int dev_id)
Returns the BluetoothAdapter matching the given dev_id or null if not found.
static final boolean VERBOSE
Definition: DBTManager.java:44
static final boolean isJVMShuttingDown()
Returns true if the JVM is shutting down, otherwise false.
static final BTManager getManager()
Returns an instance of BluetoothManager, to be used instead of constructor.
final void addChangedAdapterSetListener(final ChangedAdapterSetListener l)
Add the given ChangedAdapterSetListener to this manager.
final boolean setDefaultAdapter(final BTAdapter adapter)
Sets a default adapter to use for discovery.
final BTAdapter getDefaultAdapter()
Gets the default adapter to use for discovery.
static final void addShutdownHook(final boolean head, final Runnable runnable)
Add a shutdown hook to be performed at JVM shutdown before shutting down DBTManager instance.
One stop BTManager API entry point.
Definition: BTFactory.java:52
static final boolean VERBOSE
Verbose logging enabled or disabled.
Definition: BTFactory.java:139
static final boolean DEBUG
Debug logging enabled or disabled.
Definition: BTFactory.java:147
static< T > T doPrivileged(final PrivilegedAction< T > o)
Deprecated call to java.security.AccessController#doPrivileged(PrivilegedAction) w/o warnings.
Definition: BTFactory.java:167
BTAdapter represents one local Bluetooth Controller.
Definition: BTAdapter.java:48
int getDevID()
Returns the BluetoothAdapter's internal temporary device id.
boolean isPowered()
Returns whether the adapter is valid, plugged in and powered.
Event listener to receive change events regarding the system's BTAdapter set, e.g.
Definition: BTManager.java:53
void adapterAdded(final BTAdapter adapter)
BTAdapter was added to the system.
A thread safe singleton handler of the BTAdapter manager, e.g.
Definition: BTManager.java:36