aboutsummaryrefslogtreecommitdiffstats
path: root/java/org/direct_bt/BTFactory.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/org/direct_bt/BTFactory.java')
-rw-r--r--java/org/direct_bt/BTFactory.java530
1 files changed, 530 insertions, 0 deletions
diff --git a/java/org/direct_bt/BTFactory.java b/java/org/direct_bt/BTFactory.java
new file mode 100644
index 00000000..cee2aa7f
--- /dev/null
+++ b/java/org/direct_bt/BTFactory.java
@@ -0,0 +1,530 @@
+/**
+ * Author: Sven Gothel <[email protected]>
+ * Copyright (c) 2020 Gothel Software e.K.
+ * Copyright (c) 2020 ZAFENA AB
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package org.direct_bt;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+/**
+ * One stop {@link BTManager} API entry point.
+ * <p>
+ * Further provides access to certain property settings,
+ * see {@link #DEBUG}, {@link #VERBOSE}, {@link #DIRECTBT_CHARACTERISTIC_VALUE_CACHE_NOTIFICATION_COMPAT}.
+ * </p>
+ */
+public class BTFactory {
+
+ /**
+ * Identifier names, allowing {@link BTFactory#getBluetoothManager(ImplementationIdentifier)}
+ * to initialize the required native libraries and to instantiate the root {@link BTManager} instance.
+ * <p>
+ * The implementation class must provide the static factory method
+ * <pre>
+ * public static synchronized BluetoothManager getBluetoothManager() throws BluetoothException { .. }
+ * </pre>
+ * </p>
+ */
+ public static class ImplementationIdentifier {
+ /**
+ * Fully qualified class name for the {@link BTManager} implementation
+ * <p>
+ * The implementation class must provide the static factory method
+ * <pre>
+ * public static synchronized BluetoothManager getBluetoothManager() throws BluetoothException { .. }
+ * </pre>
+ * </p>
+ */
+ public final String BluetoothManagerClassName;
+ /** Native library basename for the implementation native library */
+ public final String ImplementationNativeLibraryBasename;
+ /** Native library basename for the Java binding native library */
+ public final String JavaNativeLibraryBasename;
+
+ public ImplementationIdentifier(final String BluetoothManagerClassName,
+ final String ImplementationNativeLibraryBasename,
+ final String JavaNativeLibraryBasename) {
+ this.BluetoothManagerClassName = BluetoothManagerClassName;
+ this.ImplementationNativeLibraryBasename = ImplementationNativeLibraryBasename;
+ this.JavaNativeLibraryBasename = JavaNativeLibraryBasename;
+ }
+
+ /**
+ * <p>
+ * Implementation compares {@link #BluetoothManagerClassName} only for equality.
+ * </p>
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(final Object other) {
+ if( null == other || !(other instanceof ImplementationIdentifier) ) {
+ return false;
+ }
+ final ImplementationIdentifier o = (ImplementationIdentifier)other;
+ return BluetoothManagerClassName.equals( o.BluetoothManagerClassName );
+ }
+
+ @Override
+ public String toString() {
+ return "ImplementationIdentifier[class "+BluetoothManagerClassName+
+ ", implLib "+ImplementationNativeLibraryBasename+
+ ", javaLib "+JavaNativeLibraryBasename+"]";
+ }
+ }
+
+ /**
+ * {@link ImplementationIdentifier} for D-Bus implementation: {@value}
+ * <p>
+ * This value is exposed for convenience, user implementations are welcome.
+ * </p>
+ */
+ public static final ImplementationIdentifier DBusImplementationID = new ImplementationIdentifier("tinyb.dbus.DBusManager", "tinyb", "javatinyb");
+
+ /**
+ * {@link ImplementationIdentifier} for direct_bt implementation: {@value}
+ * <p>
+ * This value is exposed for convenience, user implementations are welcome.
+ * </p>
+ */
+ public static final ImplementationIdentifier DirectBTImplementationID = new ImplementationIdentifier("jau.direct_bt.DBTManager", "direct_bt", "javadirect_bt");
+
+ private static final List<ImplementationIdentifier> implIDs = new ArrayList<ImplementationIdentifier>();
+
+ /**
+ * Manifest's {@link Attributes.Name#SPECIFICATION_VERSION} or {@code null} if not available.
+ */
+ public static final String getAPIVersion() { return APIVersion; }
+ private static String APIVersion;
+
+ /**
+ * Manifest's {@link Attributes.Name#IMPLEMENTATION_VERSION} or {@code null} if not available.
+ */
+ public static final String getImplVersion() { return ImplVersion; }
+ private static String ImplVersion;
+
+ /**
+ * Verbose logging enabled or disabled.
+ * <p>
+ * System property {@code org.tinyb.verbose}, boolean, default {@code false}.
+ * </p>
+ */
+ public static final boolean VERBOSE;
+
+ /**
+ * Debug logging enabled or disabled.
+ * <p>
+ * System property {@code org.tinyb.debug}, boolean, default {@code false}.
+ * </p>
+ */
+ public static final boolean DEBUG;
+
+ /**
+ * Default {@link BTMode} when initializing new adapter
+ * <p>
+ * System property {@code org.tinyb.btmode}, string, default {@code DUAL} {@link BTMode#DUAL}.
+ * </p>
+ * @since 2.0.0
+ * @implNote not implemented in tinyb.dbus.
+ */
+ public static final BTMode DEFAULT_BTMODE;
+
+ static {
+ {
+ final String v = System.getProperty("org.tinyb.verbose", "false");
+ VERBOSE = Boolean.valueOf(v);
+ }
+ {
+ final String v = System.getProperty("org.tinyb.debug", "false");
+ DEBUG = Boolean.valueOf(v);
+ }
+ {
+ final String v = System.getProperty("org.tinyb.btmode", "DUAL");
+ BTMode btMode = BTMode.DUAL;
+ try {
+ btMode = BTMode.get(v);
+ } catch (final IllegalArgumentException ex) {
+ System.err.println("Invalid BTMode '"+v+"': "+ex.getMessage());
+ }
+ DEFAULT_BTMODE = btMode;
+ }
+ implIDs.add(DirectBTImplementationID);
+ implIDs.add(DBusImplementationID);
+ }
+
+ private static ImplementationIdentifier initializedID = null;
+
+ public static synchronized void checkInitialized() {
+ if( null == initializedID ) {
+ throw new IllegalStateException("BluetoothFactory not initialized.");
+ }
+ }
+ public static synchronized boolean isInitialized() {
+ return null == initializedID;
+ }
+
+ private static synchronized void initLibrary(final ImplementationIdentifier id) {
+ if( null != initializedID ) {
+ if( id != initializedID ) {
+ throw new IllegalStateException("BluetoothFactory already initialized with "+initializedID+", can't override by "+id);
+ }
+ return;
+ }
+
+ try {
+ final Throwable[] t = { null };
+ if( !PlatformToolkit.loadLibrary(id.ImplementationNativeLibraryBasename, BTFactory.class.getClassLoader(), t) ) {
+ throw new RuntimeException("Couldn't load native library with basename <"+id.ImplementationNativeLibraryBasename+">", t[0]);
+ }
+ if( !PlatformToolkit.loadLibrary(id.JavaNativeLibraryBasename, BTFactory.class.getClassLoader(), t) ) {
+ throw new RuntimeException("Couldn't load native library with basename <"+id.JavaNativeLibraryBasename+">", t[0]);
+ }
+ } catch (final Throwable e) {
+ e.printStackTrace();
+ throw e; // fwd exception - end here
+ }
+
+ // Map all Java properties '[org.]tinyb.*' and 'direct_bt.*' to native environment.
+ try {
+ if( DEBUG ) {
+ System.err.println("BlootoothFactory: Mapping '[org.]tinyb.*' and 'direct_bt.*' properties to native environment");
+ }
+ final Properties props = AccessController.doPrivileged(new PrivilegedAction<Properties>() {
+ @Override
+ public Properties run() {
+ return System.getProperties();
+ } });
+
+ final Enumeration<?> enums = props.propertyNames();
+ while (enums.hasMoreElements()) {
+ final String key = (String) enums.nextElement();
+ if( key.startsWith("org.tinyb.") ||
+ key.startsWith("direct_bt.") || key.startsWith("tinyb.") )
+ {
+ final String value = props.getProperty(key);
+ if( DEBUG ) {
+ System.err.println(" <"+key+"> := <"+value+">");
+ }
+ setenv(key, value, true /* overwrite */);
+ }
+ }
+ } catch (final Throwable e) {
+ System.err.println("Caught exception while forwarding system properties: "+e.getMessage());
+ e.printStackTrace();
+ }
+
+ try {
+ final Manifest manifest = getManifest(BTFactory.class.getClassLoader(), new String[] { "org.tinyb" } );
+ final Attributes mfAttributes = null != manifest ? manifest.getMainAttributes() : null;
+
+ // major.minor must match!
+ final String NAPIVersion = getNativeAPIVersion();
+ final String JAPIVersion = null != mfAttributes ? mfAttributes.getValue(Attributes.Name.SPECIFICATION_VERSION) : null;
+ if ( null != JAPIVersion && JAPIVersion.equals(NAPIVersion) == false) {
+ final String[] NAPIVersionCode = NAPIVersion.split("\\D");
+ final String[] JAPIVersionCode = JAPIVersion.split("\\D");
+ if (JAPIVersionCode[0].equals(NAPIVersionCode[0]) == false) {
+ if (Integer.valueOf(JAPIVersionCode[0]) < Integer.valueOf(NAPIVersionCode[0])) {
+ throw new RuntimeException("Java library "+JAPIVersion+" < native library "+NAPIVersion+". Please update the Java library.");
+ } else {
+ throw new RuntimeException("Native library "+NAPIVersion+" < java library "+JAPIVersion+". Please update the native library.");
+ }
+ } else if (JAPIVersionCode[1].equals(NAPIVersionCode[1]) == false) {
+ if (Integer.valueOf(JAPIVersionCode[1]) < Integer.valueOf(NAPIVersionCode[1])) {
+ throw new RuntimeException("Java library "+JAPIVersion+" < native library "+NAPIVersion+". Please update the Java library.");
+ } else {
+ throw new RuntimeException("Native library "+NAPIVersion+" < java library "+JAPIVersion+". Please update the native library.");
+ }
+ }
+ }
+ initializedID = id; // initialized!
+
+ APIVersion = JAPIVersion;
+ ImplVersion = null != mfAttributes ? mfAttributes.getValue(Attributes.Name.IMPLEMENTATION_VERSION) : null;
+ if( VERBOSE ) {
+ System.err.println("tinyb2 loaded "+id);
+ System.err.println("tinyb2 java api version "+JAPIVersion);
+ System.err.println("tinyb2 native api version "+NAPIVersion);
+ if( null != mfAttributes ) {
+ final Attributes.Name[] versionAttributeNames = new Attributes.Name[] {
+ Attributes.Name.SPECIFICATION_TITLE,
+ Attributes.Name.SPECIFICATION_VENDOR,
+ Attributes.Name.SPECIFICATION_VERSION,
+ Attributes.Name.IMPLEMENTATION_TITLE,
+ Attributes.Name.IMPLEMENTATION_VENDOR,
+ Attributes.Name.IMPLEMENTATION_VERSION,
+ new Attributes.Name("Implementation-Commit") };
+ for( final Attributes.Name an : versionAttributeNames ) {
+ System.err.println(" "+an+": "+mfAttributes.getValue(an));
+ }
+ } else {
+ System.err.println(" No Manifest available;");
+ }
+ }
+ } catch (final Throwable e) {
+ System.err.println("Error querying manifest information.");
+ e.printStackTrace();
+ throw e; // fwd exception - end here
+ }
+ }
+
+ private static synchronized BTManager getBluetoothManager(final Class<?> factoryImplClass)
+ throws BTException, NoSuchMethodException, SecurityException,
+ IllegalAccessException, IllegalArgumentException, InvocationTargetException
+ {
+ final Method m = factoryImplClass.getMethod("getManager");
+ return (BTManager)m.invoke(null);
+ }
+
+ /**
+ * Registers a new {@link ImplementationIdentifier} to the internal list.
+ * The {@code id} is only added if not registered yet.
+ * @param id the {@link ImplementationIdentifier} to register
+ * @return {@code true} if the given {@link ImplementationIdentifier} has been newly added,
+ * otherwise {@code false}.
+ */
+ public static synchronized boolean registerImplementationIdentifier(final ImplementationIdentifier id) {
+ if( null == id ) {
+ return false;
+ }
+ if( implIDs.contains(id) ) {
+ return false;
+ }
+ return implIDs.add(id);
+ }
+
+ /**
+ * Returns the matching {@link ImplementationIdentifier} from the internal list or {@code null} if not found.
+ * @param fqBluetoothManagerImplementationClassName fully qualified class name for the {@link BTManager} implementation
+ */
+ public static synchronized ImplementationIdentifier getImplementationIdentifier(final String fqBluetoothManagerImplementationClassName) {
+ for(final ImplementationIdentifier id : implIDs) {
+ if( id.BluetoothManagerClassName.equals(fqBluetoothManagerImplementationClassName) ) {
+ return id;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns an initialized BluetoothManager instance using the given {@code fqBluetoothManagerImplementationClassName}
+ * to lookup a registered {@link ImplementationIdentifier}.
+ * <p>
+ * If found, method returns {@link #getBluetoothManager(ImplementationIdentifier)}, otherwise {@code null}.
+ * </p>
+ * <p>
+ * The chosen implementation can't be changed within a running implementation, an exception is thrown if tried.
+ * </p>
+ *
+ * @param fqBluetoothManagerImplementationClassName fully qualified class name for the {@link BTManager} implementation
+ * @throws BTException
+ * @throws NoSuchMethodException
+ * @throws SecurityException
+ * @throws IllegalAccessException
+ * @throws IllegalArgumentException
+ * @throws InvocationTargetException
+ * @throws ClassNotFoundException
+ * @see {@link #DBusFactoryImplClassName}
+ * @see {@link #DirectBTFactoryImplClassName}
+ */
+ public static synchronized BTManager getBluetoothManager(final String fqBluetoothManagerImplementationClassName)
+ throws BTException, NoSuchMethodException, SecurityException,
+ IllegalAccessException, IllegalArgumentException, InvocationTargetException, ClassNotFoundException
+ {
+ final ImplementationIdentifier id = getImplementationIdentifier(fqBluetoothManagerImplementationClassName);
+ if( null != id ) {
+ return getBluetoothManager(id);
+ }
+ return null;
+ }
+
+ /**
+ * Returns an initialized BluetoothManager instance using the given {@link ImplementationIdentifier}.
+ * <p>
+ * If the {@link ImplementationIdentifier} has not been {@link #registerImplementationIdentifier(ImplementationIdentifier)},
+ * it will be added to the list.
+ * </p>
+ * <p>
+ * The chosen implementation can't be changed within a running implementation, an exception is thrown if tried.
+ * </p>
+ * @param id the specific {@link ImplementationIdentifier}
+ * @throws BTException
+ * @throws NoSuchMethodException
+ * @throws SecurityException
+ * @throws IllegalAccessException
+ * @throws IllegalArgumentException
+ * @throws InvocationTargetException
+ * @throws ClassNotFoundException
+ * @see {@link #DBusFactoryImplClassName}
+ * @see {@link #DirectBTFactoryImplClassName}
+ */
+ public static synchronized BTManager getBluetoothManager(final ImplementationIdentifier id)
+ throws BTException, NoSuchMethodException, SecurityException,
+ IllegalAccessException, IllegalArgumentException, InvocationTargetException, ClassNotFoundException
+ {
+ registerImplementationIdentifier(id);
+ initLibrary(id);
+ final Class<?> factoryImpl = Class.forName(id.BluetoothManagerClassName);
+ return getBluetoothManager(factoryImpl);
+ }
+
+ /**
+ * Returns an initialized BluetoothManager instance using a D-Bus implementation.
+ * <p>
+ * Issues {@link #getBluetoothManager(ImplementationIdentifier)} using {@link #DBusImplementationID}.
+ * </p>
+ * <p>
+ * The chosen implementation can't be changed within a running implementation, an exception is thrown if tried.
+ * </p>
+ * @throws BTException
+ * @throws NoSuchMethodException
+ * @throws SecurityException
+ * @throws IllegalAccessException
+ * @throws IllegalArgumentException
+ * @throws InvocationTargetException
+ * @throws ClassNotFoundException
+ */
+ public static synchronized BTManager getDBusBluetoothManager()
+ throws BTException, NoSuchMethodException, SecurityException,
+ IllegalAccessException, IllegalArgumentException, InvocationTargetException, ClassNotFoundException
+ {
+ return getBluetoothManager(DBusImplementationID);
+ }
+
+ /**
+ * Returns an initialized BluetoothManager instance using the DirectBT implementation.
+ * <p>
+ * Issues {@link #getBluetoothManager(ImplementationIdentifier)} using {@link #DirectBTImplementationID}.
+ * </p>
+ * <p>
+ * The chosen implementation can't be changed within a running implementation, an exception is thrown if tried.
+ * </p>
+ * @throws BTException
+ * @throws NoSuchMethodException
+ * @throws SecurityException
+ * @throws IllegalAccessException
+ * @throws IllegalArgumentException
+ * @throws InvocationTargetException
+ * @throws ClassNotFoundException
+ */
+ public static synchronized BTManager getDirectBTBluetoothManager()
+ throws BTException, NoSuchMethodException, SecurityException,
+ IllegalAccessException, IllegalArgumentException, InvocationTargetException, ClassNotFoundException
+ {
+ return getBluetoothManager(DirectBTImplementationID);
+ }
+
+ private static final Manifest getManifest(final ClassLoader cl, final String[] extensions) {
+ final Manifest[] extManifests = new Manifest[extensions.length];
+ try {
+ final Enumeration<URL> resources = cl.getResources("META-INF/MANIFEST.MF");
+ while (resources.hasMoreElements()) {
+ final URL resURL = resources.nextElement();
+ if( DEBUG ) {
+ System.err.println("resource: "+resURL);
+ }
+ final InputStream is = resURL.openStream();
+ final Manifest manifest;
+ try {
+ manifest = new Manifest(is);
+ } finally {
+ try {
+ is.close();
+ } catch (final IOException e) {}
+ }
+ final Attributes attributes = manifest.getMainAttributes();
+ if(attributes != null) {
+ final String attributesExtName = attributes.getValue( Attributes.Name.EXTENSION_NAME );
+ if( DEBUG ) {
+ System.err.println("resource: "+resURL+", attributes extName "+attributesExtName+", count "+attributes.size());
+ final Set<Object> keys = attributes.keySet();
+ for(final Iterator<Object> iter=keys.iterator(); iter.hasNext(); ) {
+ final Attributes.Name key = (Attributes.Name) iter.next();
+ final String val = attributes.getValue(key);
+ System.err.println(" "+key+": "+val);
+ }
+ }
+ for(int i=0; i < extensions.length && null == extManifests[i]; i++) {
+ final String extension = extensions[i];
+ if( extension.equals( attributesExtName ) ) {
+ if( 0 == i ) {
+ return manifest; // 1st one has highest prio - done
+ }
+ extManifests[i] = manifest;
+ }
+ }
+ }
+ }
+ } catch (final IOException ex) {
+ throw new RuntimeException("Unable to read manifest.", ex);
+ }
+ for(int i=1; i<extManifests.length; i++) {
+ if( null != extManifests[i] ) {
+ return extManifests[i];
+ }
+ }
+ return null;
+ }
+
+ private native static String getNativeAPIVersion();
+ private native static void setenv(String name, String value, boolean overwrite);
+}
+
+/** \example DBTScanner10.java
+ * This Java scanner example uses the Direct-BT fully event driven workflow
+ * and adds multithreading, i.e. one thread processes each found device found
+ * as notified via the event listener.
+ * <p>
+ * This example represents the recommended utilization of Direct-BT.
+ * </p>
+ */
+
+/** \example ScannerTinyB00.java
+ * This Java scanner example is a TinyB backward compatible and not fully event driven.
+ * It simply polls found devices and shows certain results.
+ * <p>
+ * This example does not represent the recommended utilization of Direct-BT.
+ * </p>
+ */
+
+/** \example ScannerTinyB01.java
+ * This Java scanner example is a TinyB backward compatible and not fully event driven.
+ * It simply polls found devices and shows certain results.
+ * However, the AdapterStatusListener is attached if supported.
+ * <p>
+ * This example does not represent the recommended utilization of Direct-BT.
+ * </p>
+ */