it.scoppelletti.mobilepower.bluetooth.BTManager.java Source code

Java tutorial

Introduction

Here is the source code for it.scoppelletti.mobilepower.bluetooth.BTManager.java

Source

/*
 * Copyright (C) 2014 Dario Scoppelletti, <http://www.scoppelletti.it/>.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package it.scoppelletti.mobilepower.bluetooth;

import android.bluetooth.*;
import android.content.*;
import android.os.*;
import org.apache.commons.lang3.*;
import org.slf4j.*;
import it.scoppelletti.mobilepower.os.*;

/**
 * Gestore dei dispositivi Bluetooth.
 * 
 * @since 1.0
 */
public class BTManager {
    private static final Logger myLogger = LoggerFactory.getLogger("BTManager");
    private final Context myCtx;
    private BluetoothAdapter myBTAdapter;
    private BTTask myPendingTask;
    private OnBTListener myOnListener;

    /**
     * Costruttore.
     * 
     * @param ctx Contesto.
     */
    public BTManager(Context ctx) {
        if (ctx == null) {
            throw new NullPointerException("Argument ctx is null.");
        }

        myCtx = ctx;
    }

    /**
     * Restituisce il contesto.
     * 
     * @return Oggetto.
     */
    protected final Context getContext() {
        return myCtx;
    }

    /**
     * Imposta il gestore dell&rsquo;evento di dispositivo rilevato.
     * 
     * @param listener Oggetto.
     */
    public final void setOnBTListener(OnBTListener obj) {
        myOnListener = obj;
    }

    /**
     * Inizializza il protocollo Bluetooth.
     * 
     * <P>Il metodo statico {@code getDefaultAdapter} della classe
     * {@code BluetoothAdpter} verifica una-tantum di essere eseguito
     * all&rsquo;interno di un thread che abbia inizializzato la coda dei 
     * messaggi (anche se in realt&agrave; non sembra che ne abbia bisogno), e,
     * in caso contrario, inoltra un&rsquo;eccezione.<BR>
     * Se il metodo {@code getDefaultAdapter} &egrave; eseguito per la prima
     * volta all&rsquo;interno di un thread di lavoro, &egrave; pertanto
     * inoltrata l&rsquo;eccezione, e l&rsquo;unico modo per evitarlo sembra
     * essere inserire una <I>esecuzione di inizializzazione</I> del metodo
     * {@code getDefaultAdapter} nel thread principale (normalmente nel metodo
     * {@code onCreate} dell&rsquo;attivit&agrave; principale.</P>
     * 
     * <P>L&rsquo;inizializzazione del protocollo Bluetooth non sembra essere
     * necessaria nel contesto di un servizio locale.</P>
     */
    public static void initBluetooth() {
        // stackoverflow.com/questions/5920578, Kocus, 07/05/2011
        // 
        // BluetoothAdapter.getDefault() throwing RuntimeException while not in
        // Activity
        // 
        // When I'm trying to get default bluetooth adapter while i'm NOT in
        // Activity, but in TimerTask (created inside Service) by using:
        // 
        // BluetoothAdapter.getDefaultAdapter();
        //
        // I get the following exception:
        // 
        // Exception while invoking java.lang.RuntimeException: Can't create
        // handler inside thread that has not called Looper.prepare()
        // My application do not have any activity - so is there any possibility
        // to get this adapter away from Activity?
        //
        // - Toland H, 23/02/2013
        // 
        // This appears to be a bug in Android and still exists in Android 4.0
        // (Ice Cream Sandwich)
        // 
        // To workaround this and be able to call
        // BluetoothAdapter.getDefaultAdapter() from a worker thread (e.g.
        // AsyncTask), all you have to do is call
        // BluetoothAdapter.getDefaultAdapter() once on the main UI thread (e.g.
        // inside the onCreate() of your current activity).
        //
        // The RuntimeException is only thrown during initialization, and
        // BluetoothAdapter.getDefaultAdapter() only initializes the first time
        // you call it. Subsequent calls to it will succeed, even in background
        // threads.
        //
        // - user2304686, 21/04/2013
        // 
        // calling BluetoothAdapter.getDefaultAdapter() in the UI thread works,
        // but is not very practical. I have tried the workaround with a fake
        // Activity, but since I hate such workarounds, I decided to READ what
        // the error message really says and it is nothing more than that the
        // thread didn't call Looper.prepare().
        // So calling Looper.prepare() just before calling
        // BluetoothAdapter.getDefaultAdapter() should solve the problem
        // anywhere, not just in a UI thread.
        // Works fine for me so far.
        //
        // - Flipper, 02/10/2013
        // 
        // Beware of a gotcha that exists in 2.3.x, but which has been fixed in
        // 4.x: if you call BluetoothAdapter.getDefaultAdapter() on any thread
        // other than the main application thread, that thread must call
        // Looper.prepare() and also subsequently Looper.loop().
        // Failing to do so will cause at least one problem that I ran into:
        // accept() will succeed the first time you try to connect, but then not
        // succeed on successive attempts, even after using close() on the
        // ServerSocket.
        // This happens because in the older implementation of BluetoothAdapter,
        // the cleanup of the SDP entry occurs by way of a message posted to a
        // handler created on the thread where getDefaultAdapter() is called.

        if (Build.VERSION.SDK_INT < BuildCompat.VERSION_CODES.JELLY_BEAN) {
            BluetoothAdapter.getDefaultAdapter();
        }
    }

    /**
     * Restituisce il nome di un dispositivo.
     * 
     * @param  device Dispositivo.
     * @return        Nome del dispositivo. Se il nome del dispositivo non 
     *                &egrave; determinabile, restituisce l&rsquo;indirizzo del
     *                dispositivo.
     */
    public static String getName(BluetoothDevice device) {
        String s;

        if (device == null) {
            throw new NullPointerException("Argument device is null.");
        }

        s = device.getName();
        if (StringUtils.isBlank(s)) {
            s = device.getAddress();
        }

        return s;
    }

    /**
     * Esegue un&rsquo;operazione.
     * 
     * @param task Operazione.
     */
    public final void run(BTTask task) {
        if (task == null) {
            throw new NullPointerException("Argument task is null.");
        }

        myPendingTask = task;
        attachAdapter();
    }

    /**
     * Esegue l&rsquo;eventuale operazione richiesta. 
     */
    protected final void onRun() {
        try {
            cancelDiscovery();

            if (myPendingTask != null) {
                myPendingTask.run(myBTAdapter);
                myPendingTask = null;
            }
        } finally {
            detachAdapter();
        }
    }

    /**
     * Collega il gestore dei dispositivi.
     */
    private void attachAdapter() {
        myBTAdapter = BluetoothAdapter.getDefaultAdapter();
        if (myBTAdapter == null) {
            myLogger.warn("Bluetooth not supported.");
            if (myOnListener != null) {
                myOnListener.onNotSupported();
            }
            return;
        }

        if (myBTAdapter.isEnabled()) {
            onRun();
        } else {
            onDisabled();
        }
    }

    /**
     * Gestisce l&rsquo;evento di Bluetooth disabilitato.
     */
    protected void onDisabled() {
        detachAdapter();
        myLogger.warn("Bluetooth disabled.");
        if (myOnListener != null) {
            myOnListener.onDisabled();
        }
    }

    /**
     * Scollega il gestore dei dispositivi.
     */
    protected final void detachAdapter() {
        cancelDiscovery();
        myBTAdapter = null;
    }

    /**
     * Annulla la ricerca dei dispositivi BT rilevabili.
     */
    private void cancelDiscovery() {
        if (myBTAdapter == null) {
            return;
        }

        if (!myBTAdapter.isDiscovering()) {
            return;
        }

        if (myBTAdapter.cancelDiscovery()) {
            myLogger.trace("Discovery of Bluetooth devices is cancelled.");
        } else {
            myLogger.warn("Failed to cancel discovery of Bluetooth devices.");
        }
    }
}