// Path: src/se/bitcraze/crazyflie/lib/crtp/CrtpDriver.java
// public abstract class CrtpDriver {
// 
//     protected Set<ConnectionListener> mConnectionListeners = new CopyOnWriteArraySet<ConnectionListener>();
// 
//     /**
//      * Driver constructor. Throw an exception if the driver is unable to open the URI
//      */
//     public CrtpDriver() {
//     }
// 
//     /**
//      * Connect the driver
//      *
//      * @throws IOException
//      */
//     public abstract void connect() throws IOException;
// 
//     /**
//      * Close the link
//      */
//     public abstract void disconnect();
// 
//     /**
//      * Check whether the link is connected.
//      *
//      * @return <code>true</code> if the link is connected.
//      */
//     public abstract boolean isConnected();
// 
//     /**
//      * Send a CRTP packet
//      *
//      * @param packet
//      */
//     public abstract void sendPacket(CrtpPacket packet);
// 
//     /**
//      * Receive a CRTP packet.
//      *
//      * @param wait The time to wait for a packet in milliseconds. -1 means forever
//      * @return One CRTP packet or None if no packet has been received.
//      */
//     public abstract CrtpPacket receivePacket(int wait);
// 
//     /* CONNECTION LISTENER */
// 
//     public void addConnectionListener(ConnectionListener listener) {
//         this.mConnectionListeners.add(listener);
//     }
// 
//     public void removeConnectionListener(ConnectionListener listener) {
//         this.mConnectionListeners.remove(listener);
//     }
// 
//     /**
//      * Notify all registered listeners about a requested connection
//      */
//     protected void notifyConnectionRequested(){
//         for (ConnectionListener cl : this.mConnectionListeners) {
//             cl.connectionRequested();
//         }
//     }
// 
//     /**
//      * Notify all registered listeners about a connect.
//      */
//     public void notifyConnected(){
//         for (ConnectionListener cl : this.mConnectionListeners) {
//             cl.connected();
//         }
//     }
//     /**
//      * Notify all registered listeners about a finished setup.
//      */
//     public void notifySetupFinished(){
//         for (ConnectionListener cl : this.mConnectionListeners) {
//             cl.setupFinished();
//         }
//     }
// 
//     /**
//      * Notify all registered listeners about a failed connection attempt.
//      *
//      * @param msg
//      */
//     protected void notifyConnectionFailed(String msg) {
//         for (ConnectionListener cl : this.mConnectionListeners) {
//             cl.connectionFailed(msg);
//         }
//     }
// 
//     /**
//      * Notify all registered listeners about a lost connection.
//      *
//      * @param msg
//      */
//     protected void notifyConnectionLost(String msg) {
//         for (ConnectionListener cl : this.mConnectionListeners) {
//             cl.connectionLost(msg);
//         }
//     }
// 
//     /**
//      * Notify all registered listeners about a disconnect.
//      */
//     protected void notifyDisconnected() {
//         for (ConnectionListener cl : this.mConnectionListeners) {
//             cl.disconnected();
//         }
//     }
// 
//     /**
//      * Notify all registered listeners about a link quality update.
//      *
//      * @param percent quality of the link (0 = connection lost, 100 = good)
//      */
//     protected void notifyLinkQualityUpdated(int percent) {
//         for (ConnectionListener cl : this.mConnectionListeners) {
//             cl.linkQualityUpdated(percent);
//         }
//     }
// 
// }


import java.util.Arrays;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Handler;
import android.support.annotation.RequiresApi;
import se.bitcraze.crazyflie.lib.crtp.CrtpDriver;
import se.bitcraze.crazyflie.lib.crtp.CrtpPacket;
                try {
                    mInQueue.put(packet);
                } catch (InterruptedException ie) {
                    //
                }
            }
        }

        @Override
        public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status){
            if (status == BluetoothGatt.GATT_SUCCESS) {
                //mLogger.debug(String.format("BLE ReadRSSI [%d]", rssi));
                int percentage = 2*(rssi+90); // based on guesstimate
                notifyLinkQualityUpdated(limit(percentage));
            }
        }
    };

    /**
     * Limit range of int between 0 and 100
     *
     * @param value number
     * @return number between 0 and 100
     */
    private int limit(int value) {
        return Math.max(0, Math.min(value, 100));
    }

    private TimerTask rssiTask = new TimerTask() {
        @Override
// Path: src/se/bitcraze/crazyflie/lib/crtp/CrtpDriver.java
// public abstract class CrtpDriver {
// 
//     protected Set<ConnectionListener> mConnectionListeners = new CopyOnWriteArraySet<ConnectionListener>();
// 
//     /**
//      * Driver constructor. Throw an exception if the driver is unable to open the URI
//      */
//     public CrtpDriver() {
//     }
// 
//     /**
//      * Connect the driver
//      *
//      * @throws IOException
//      */
//     public abstract void connect() throws IOException;
// 
//     /**
//      * Close the link
//      */
//     public abstract void disconnect();
// 
//     /**
//      * Check whether the link is connected.
//      *
//      * @return <code>true</code> if the link is connected.
//      */
//     public abstract boolean isConnected();
// 
//     /**
//      * Send a CRTP packet
//      *
//      * @param packet
//      */
//     public abstract void sendPacket(CrtpPacket packet);
// 
//     /**
//      * Receive a CRTP packet.
//      *
//      * @param wait The time to wait for a packet in milliseconds. -1 means forever
//      * @return One CRTP packet or None if no packet has been received.
//      */
//     public abstract CrtpPacket receivePacket(int wait);
// 
//     /* CONNECTION LISTENER */
// 
//     public void addConnectionListener(ConnectionListener listener) {
//         this.mConnectionListeners.add(listener);
//     }
// 
//     public void removeConnectionListener(ConnectionListener listener) {
//         this.mConnectionListeners.remove(listener);
//     }
// 
//     /**
//      * Notify all registered listeners about a requested connection
//      */
//     protected void notifyConnectionRequested(){
//         for (ConnectionListener cl : this.mConnectionListeners) {
//             cl.connectionRequested();
//         }
//     }
// 
//     /**
//      * Notify all registered listeners about a connect.
//      */
//     public void notifyConnected(){
//         for (ConnectionListener cl : this.mConnectionListeners) {
//             cl.connected();
//         }
//     }
//     /**
//      * Notify all registered listeners about a finished setup.
//      */
//     public void notifySetupFinished(){
//         for (ConnectionListener cl : this.mConnectionListeners) {
//             cl.setupFinished();
//         }
//     }
// 
//     /**
//      * Notify all registered listeners about a failed connection attempt.
//      *
//      * @param msg
//      */
//     protected void notifyConnectionFailed(String msg) {
//         for (ConnectionListener cl : this.mConnectionListeners) {
//             cl.connectionFailed(msg);
//         }
//     }
// 
//     /**
//      * Notify all registered listeners about a lost connection.
//      *
//      * @param msg
//      */
//     protected void notifyConnectionLost(String msg) {
//         for (ConnectionListener cl : this.mConnectionListeners) {
//             cl.connectionLost(msg);
//         }
//     }
// 
//     /**
//      * Notify all registered listeners about a disconnect.
//      */
//     protected void notifyDisconnected() {
//         for (ConnectionListener cl : this.mConnectionListeners) {
//             cl.disconnected();
//         }
//     }
// 
//     /**
//      * Notify all registered listeners about a link quality update.
//      *
//      * @param percent quality of the link (0 = connection lost, 100 = good)
//      */
//     protected void notifyLinkQualityUpdated(int percent) {
//         for (ConnectionListener cl : this.mConnectionListeners) {
//             cl.linkQualityUpdated(percent);
//         }
//     }
// 
// }


// Path: src/se/bitcraze/crazyfliecontrol/ble/BleLink.java
import java.util.Arrays;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Handler;
import android.support.annotation.RequiresApi;
import se.bitcraze.crazyflie.lib.crtp.CrtpDriver;
import se.bitcraze.crazyflie.lib.crtp.CrtpPacket;

                try {
                    mInQueue.put(packet);
                } catch (InterruptedException ie) {
                    //
                }
            }
        }

        @Override
        public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status){
            if (status == BluetoothGatt.GATT_SUCCESS) {
                //mLogger.debug(String.format("BLE ReadRSSI [%d]", rssi));
                int percentage = 2*(rssi+90); // based on guesstimate
                notifyLinkQualityUpdated(limit(percentage));
            }
        }
    };

    /**
     * Limit range of int between 0 and 100
     *
     * @param value number
     * @return number between 0 and 100
     */
    private int limit(int value) {
        return Math.max(0, Math.min(value, 100));
    }

    private TimerTask rssiTask = new TimerTask() {
        @Override
        public void run() {