/*
 * Bluegiga’s Bluetooth Smart Android SW for Bluegiga BLE modules
 * Contact: [email protected].
 *
 * This is free software distributed under the terms of the MIT license reproduced below.
 *
 * Copyright (c) 2013, Bluegiga Technologies
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files ("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:
 *
 * THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
 * ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT
 * NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR PURPOSE.
 */
package com.siliconlabs.bledemo.activity;


import android.Manifest;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.app.Dialog;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
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.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Color;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;

import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.view.ViewCompat;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;

import android.provider.OpenableColumns;
import android.text.TextUtils;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.webkit.WebView;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.Chronometer;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RadioButton;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.gms.appindexing.Action;
import com.google.android.gms.appindexing.AppIndex;
import com.google.android.gms.appindexing.Thing;
import com.google.android.gms.common.api.GoogleApiClient;
import com.siliconlabs.bledemo.OtaFileType;
import com.siliconlabs.bledemo.dialogs.ErrorDialog;
import com.siliconlabs.bledemo.mappings.Mapping;
import com.siliconlabs.bledemo.mappings.MappingType;
import com.siliconlabs.bledemo.mappings.MappingsEditDialog;
import com.siliconlabs.bledemo.R;
import com.siliconlabs.bledemo.adapters.ConnectionsAdapter;
import com.siliconlabs.bledemo.adapters.LogAdapter;
import com.siliconlabs.bledemo.ble.BlueToothService;
import com.siliconlabs.bledemo.ble.BluetoothDeviceInfo;
import com.siliconlabs.bledemo.ble.TimeoutGattCallback;
import com.siliconlabs.bledemo.bluetoothdatamodel.datatypes.Characteristic;
import com.siliconlabs.bledemo.bluetoothdatamodel.datatypes.Descriptor;
import com.siliconlabs.bledemo.bluetoothdatamodel.datatypes.Service;
import com.siliconlabs.bledemo.bluetoothdatamodel.parsing.Common;
import com.siliconlabs.bledemo.bluetoothdatamodel.parsing.Converters;
import com.siliconlabs.bledemo.bluetoothdatamodel.parsing.Engine;
import com.siliconlabs.bledemo.fragment.FragmentCharacteristicDetail;
import com.siliconlabs.bledemo.fragment.LogFragmentConnected;
import com.siliconlabs.bledemo.interfaces.MappingCallback;
import com.siliconlabs.bledemo.interfaces.ServicesConnectionsCallback;
import com.siliconlabs.bledemo.log.TimeoutLog;
import com.siliconlabs.bledemo.toolbars.ConnectionsFragment;
import com.siliconlabs.bledemo.toolbars.LoggerFragment;
import com.siliconlabs.bledemo.toolbars.ToolbarCallback;
import com.siliconlabs.bledemo.utils.BLEUtils;
import com.siliconlabs.bledemo.utils.BLEUtils.Notifications;
import com.siliconlabs.bledemo.utils.Constants;
import com.siliconlabs.bledemo.utils.FilterDeviceParams;
import com.siliconlabs.bledemo.utils.SharedPrefUtils;
import com.siliconlabs.bledemo.utils.ToolbarName;
import com.siliconlabs.bledemo.views.ServiceItemContainer;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;

import butterknife.ButterKnife;
import butterknife.InjectView;

public class DeviceServicesActivity extends AppCompatActivity implements ServicesConnectionsCallback {
    private static final String ABOUT_DIALOG_HTML_ASSET_FILE_PATH = "file:///android_asset/about.html";
    private static final String CHARACTERISTIC_ADD_FRAGMENT_TRANSACTION_ID = "characteristicdetail";
    private static final int UI_CREATION_DELAY = 0;
    private static final int GATT_FETCH_ON_SERVICE_DISCOVERED_DELAY = 875;
    private static final String PROPERTY_ICON_TAG = "characteristicpropertyicon";
    private static final String PROPERTY_NAME_TAG = "characteristispropertyname";
    private Handler handler;

    private static final int WRITE_EXTERNAL_STORAGE_REQUEST_PERMISSION = 300;

    private static final int FILE_CHOOSER_REQUEST_CODE = 9999;

    /**
     * Services UUIDs
     */
    public static UUID ota_service = UUID.fromString("1d14d6ee-fd63-4fa1-bfa4-8f47b42119f0");
    private UUID ota_control = UUID.fromString("f7bf3564-fb6d-4e53-88a4-5e37e0326063");
    private UUID ota_data = UUID.fromString("984227f3-34fc-4045-a5d0-2c581f81a153");
    private UUID fw_version = UUID.fromString("4f4a2368-8cca-451e-bfff-cf0e2ee23e9f");
    private UUID ota_version = UUID.fromString("4cc07bcf-0868-4b32-9dad-ba4cc41e5316");
    private UUID homekit_descriptor = UUID.fromString("dc46f0fe-81d2-4616-b5d9-6abdd796939a");
    private UUID homekit_service = UUID.fromString("0000003e-0000-1000-8000-0026bb765291");

    private BluetoothDevice bluetoothDevice = null;
    private int generatedId = 10000;
    private boolean serviceHasBeenSet;
    private BlueToothService service;
    private BlueToothService.Binding bluetoothBinding;
    private BluetoothGatt bluetoothGatt;
    private Dialog dialogLicense;
    private Dialog newPriority;
    private Dialog newMTU;
    private ErrorDialog errorDialog;
    private BluetoothAdapter bluetoothAdapter = null;
    private BluetoothManager bluetoothManager = null;
    private BluetoothLeScanner bluetoothLeScanner = null;

    private HashMap<String, Mapping> characteristicNamesMap;
    private HashMap<String, Mapping> serviceNamesMap;
    private SharedPrefUtils sharedPrefUtils;

    private HashMap<Integer, FragmentCharacteristicDetail> characteristicFragments = new HashMap<>();

    /**OTA MenuButton*/
//    MenuItem ota_button;

    /**
     * LOG
     */
    private Thread logupdate;
    private volatile boolean running = false;
    private StringBuilder substraction = new StringBuilder();
    private TextView tv;

    /**
     * OTA Progress
     */
    private Dialog otaProgress;
    private ProgressBar progressBar;
    private Chronometer chrono;
    private TextView dataRate;
    private TextView datasize;
    private TextView filename;
    private TextView steps;
    private ProgressBar uploadimage;
    private Button OTAStart;


    /**
     * OTA Setup
     */
    private Dialog otaSetup;
    private RadioButton reliabilityRB;
    private RadioButton speedRB;
    private Button partialOTA;
    private Button fullOTA;
    private Button OTA_OK;
    private SeekBar requestMTU;
    private SeekBar delaySeekBar;
    private int delayNoResponse = 1;
    private TextView sizename;
    private TextView mtuname;
    private CheckBox reliableWrite;
    private TextView delayText;

    private Button appFileButton;
    private Button appLoaderFileButton;
    private OtaFileType currentOtaFileType;

    private int priority = 2;

    private int requestMTUValue;

    /**
     * File Selection
     */
    private String appPath = "";
    private String stackPath = "";

    /**
     * Loading Dialog
     */
    private Dialog loadingdialog;
    private TextView loadingLog;
    private TextView loadingHeader;
    private ProgressBar loadingimage;

    /**
     * Global Variables
     */
    private int MTU = 247;
    private int MTU_divisible = 0;
    private long otatime = 0;
    private int pack = 0;
    private byte[] otafile;
    private String reconnectaddress;
    private long delayToConnect = 0;
    private int onScanCallback = 0;

    /**
     * Global Booleans
     */
    private boolean reliable = true;
    private boolean boolFullOTA = false;
    private boolean boolOTAbegin = false;
    private boolean connected = false;
    private boolean boolOTAdata = false;
    private boolean UICreated = false;
    private boolean discoverTimeout = true;
    private boolean ota_mode = false;
    private boolean boolrequest_mtu = false;
    private boolean ota_process = false;
    private boolean boolrefresh_services = false;
    private boolean disconnect_gatt = false;
    private boolean disconnectionTimeout = false;
    private boolean homekit = false;
    private boolean doubleStepUpload = false;
    private boolean otaMode = false;

    private BluetoothGattDescriptor kit_descriptor;

    private FragmentCharacteristicDetail currentWriteReadFragment;

    private Map<String, ServiceItemContainer> serviceItemContainers;

    private ConnectionsFragment connectionsFragment;
    private ConnectionsAdapter connectionsAdapter;
    private LoggerFragment loggerFragment;
    private boolean btToolbarOpened = false;
    private ToolbarName btToolbarOpenedName = null;

    private static final int TOOLBAR_OPEN_PERCENTAGE = 95;
    private static final int TOOLBAR_CLOSE_PERCENTACE = 95;

    private final Runnable DFU_OTA_UPLOAD = new Runnable() {
        @Override
        public void run() {
            DFUMode("OTAUPLOAD");
        }
    };
    private final Runnable WRITE_OTA_CONTROL_ZERO = new Runnable() {
        @Override
        public void run() {
            writeOtaControl((byte) 0x00);
        }
    };


    private String deviceAddress;
    @InjectView(R.id.toolbar)
    Toolbar toolbar;
    @InjectView(R.id.services_container)
    LinearLayout servicesContainer;
    @InjectView(R.id.servicesWrapper)
    RelativeLayout servicesWrapper;
    @InjectView(R.id.scrollViewWrapper)
    RelativeLayout scrollViewWrapper;
    @InjectView(R.id.loading_container)
    public RelativeLayout loadingContainer;
    @InjectView(R.id.loading_anim_gradient_right_container)
    public LinearLayout loadingGradientContainer;
    @InjectView(R.id.loading_bar_container)
    public RelativeLayout loadingBarContainer;
    @InjectView(R.id.framelayout_container)
    RelativeLayout frameLayoutContainerRL;
    @InjectView(R.id.frame_layout)
    FrameLayout frameLayout;
    @InjectView(R.id.linearlayout_connections)
    LinearLayout connectionsLL;
    @InjectView(R.id.bluetooth_browser_background)
    RelativeLayout bluetoothBrowserBackgroundRL;
    @InjectView(R.id.textview_connections)
    TextView connectionsTV;
    @InjectView(R.id.imageview_connections)
    ImageView connectionsIV;
    @InjectView(R.id.linearlayout_log)
    LinearLayout logLL;
    @InjectView(R.id.textview_log)
    TextView logTV;
    @InjectView(R.id.imageview_log)
    ImageView logIV;
    @InjectView(R.id.rssi_text_view)
    TextView rssiTV;
    @InjectView(R.id.rssi_image_view)
    ImageView rssiIV;


    /**
     * BLUETOOTH GATT CALLBACKS
     *********************************************************/
    private TimeoutGattCallback gattCallback = new TimeoutGattCallback() {
        @Override
        public void onReadRemoteRssi(final BluetoothGatt gatt, final int rssi, int status) {
            if (!otaMode) {
                super.onReadRemoteRssi(gatt, rssi, status);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Log.d("onReadRemoteRssi", "RSSI: " + rssi);
                        rssiTV.setText(getResources().getString(R.string.n_dBm, rssi));
                    }
                });
            }
        }

        @Override
        public void onTimeout() {
            Constants.LOGS.add(new TimeoutLog());
            super.onTimeout();
            Log.d("gattCallback", "onTimeout");
        }

        @Override //CALLBACK TO REQUEST MTU
        public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
            Log.d("onMtuChanged", "MTU: " + mtu + " - status: " + status);

            if (status == 0) { //NO ERRORS
                MTU = mtu;

                bluetoothGatt.requestConnectionPriority(priority);

                if (boolrequest_mtu) { //Request MTU From rounded_button_red menu
                    MTUonButtonMenu();
                } else if (ota_process && !boolrequest_mtu) {
                    if (ota_mode && newMTU.isShowing()) { //Reopen OTA Setup
                        reopenOTASetup();
                    }
                    if (ota_mode) { //Reset OTA Progress
                        resetOTAProgress();
                    }
                }
            } else { //ERROR HANDLING
                final int error = status;
                Log.d("RequestMTU", "Error: " + error);
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                Toast.makeText(getBaseContext(), "ERROR REQUESTING MTU: " + error, Toast.LENGTH_LONG).show();
                            }
                        });
                    }
                });
                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        disconnectGatt(bluetoothGatt);
                    }
                }, 2000);
            }
        }

        @Override //CALLBACK ON CONNECTION STATUS CHANGES
        public void onConnectionStateChange(final BluetoothGatt gatt, int status, int newState) {
            updateCountOfConnectedDevices();
            if (bluetoothGatt != null) {
                if (!bluetoothGatt.getDevice().getAddress().equals(gatt.getDevice().getAddress())) {
                    return;
                }
            }
            super.onConnectionStateChange(gatt, status, newState);
            Log.d("onConnectionStateChange", "status = " + status + " - newState = " + newState);
            switch (newState) {
                case BluetoothGatt.STATE_CONNECTED: //Handling Connections
                    connected = true;
                    Log.d("onConnectionStateChange", "CONNECTED");
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            if (!loadingdialog.isShowing()) {
                                Toast.makeText(DeviceServicesActivity.this, "DEVICE CONNECTED", Toast.LENGTH_LONG).show();
                            }
                        }
                    });

                    if (ota_process) { //After OTA process started
                        Log.d("Address", "" + gatt.getDevice());
                        Log.d("Name", "" + gatt.getDevice().getName());

                        if (gatt.getServices().isEmpty()) {
                            handler.postDelayed(new Runnable() {
                                @Override
                                public void run() {
                                    bluetoothGatt = null; //It's going to be equal gatt in Discover Services Callback...
                                    Log.d("onConnected", "Start Services Discovery: " + gatt.discoverServices());
                                }
                            }, 250);
                            discoverTimeout = true;
                            Runnable timeout = new Runnable() { //Discover Services Timeout
                                @Override
                                public void run() {
                                    handler.postDelayed(new Runnable() {
                                        @Override
                                        public void run() {
                                            if (discoverTimeout) {
                                                disconnectGatt(gatt);
                                                runOnUiThread(new Runnable() {
                                                    @Override
                                                    public void run() {
                                                        Toast.makeText(getBaseContext(), "DISCOVER SERVICES TIMEOUT", Toast.LENGTH_LONG).show();
                                                    }
                                                });
                                            }
                                        }
                                    }, 25000);
                                }
                            };
                            new Thread(timeout).start();
                        }
                    }
                    break;
                case BluetoothGatt.STATE_DISCONNECTED://Handling Disonnections
                    connected = false;
                    discoverTimeout = false;
                    final int error = status;
                    disconnectionTimeout = false;

                    if (status != 0 && otaMode && errorDialog == null) {
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                errorDialog = new ErrorDialog(error, new ErrorDialog.OtaErrorCallback() {
                                    @Override
                                    public void onDismiss() {
                                        exit(bluetoothGatt);
                                    }
                                });
                                errorDialog.show(getSupportFragmentManager(), "ota_error_dialog");
                            }
                        });
                    } else {
                        if (disconnect_gatt) {
                            exit(gatt);
                        }

                        if (ota_process || boolOTAbegin || boolFullOTA) {
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    if (loadingdialog.isShowing()) {
                                        loadingLog.setText("Rebooting...");
                                        handler.postDelayed(new Runnable() {
                                            @Override
                                            public void run() {
                                                runOnUiThread(new Runnable() {
                                                    @Override
                                                    public void run() {
                                                        loadingLog.setText("Waiting...");
                                                    }
                                                });
                                            }
                                        }, 1500);
                                    }
                                }
                            });
                        }

                        if (otaSetup != null) if (otaSetup.isShowing()) {
                            exit(gatt);
                        }

                        if (gatt != null && gatt.getServices().isEmpty()) {
                            exit(gatt);

                        }
                        if (gatt != null && !boolFullOTA && !boolOTAbegin && !ota_process) {
                            exit(gatt);
                        }
                    }
                    break;
                case BluetoothGatt.STATE_CONNECTING:
                    Log.d("onConnectionStateChange", "Connecting...");
                    break;
            }
        }

        @Override //CALLBACK ON CHARACTERISTIC READ
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            if (currentWriteReadFragment != null) {
                currentWriteReadFragment.onActionDataAvailable(characteristic.getUuid().toString());
            }

            Log.i("Callback", "OnCharacteristicRead: " + Converters.getHexValue(characteristic.getValue()) + " Status: " + status);

            if (characteristic == (bluetoothGatt.getService(ota_service).getCharacteristic(ota_control))) {
                byte[] value = characteristic.getValue();
                if (value[2] == (byte) 0x05) {
                    Log.d("homekit_descriptor", "Insecure Connection");
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(getBaseContext(), "Error: Not a Homekit Secure Connection", Toast.LENGTH_SHORT).show();
                        }
                    });
                } else if (value[2] == (byte) 0x04) {
                    Log.d("homekit_descriptor", "Wrong Address");
                } else if (value[2] == (byte) 0x00) {
                    Log.d("homekit_descriptor", "Entering in DFU_Mode...");
                    if (ota_mode && ota_process) {
                        Log.d("OTAUPLOAD", "Sent");
                        runOnUiThread(checkbeginrunnable);
                        handler.removeCallbacks(DFU_OTA_UPLOAD);
                        handler.postDelayed(DFU_OTA_UPLOAD, 500);
                    } else if (!ota_mode && ota_process) {
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                loadingLog.setText("Resetting...");
                                showLoading();
                                animaloading();
                                Constants.ota_button.setVisible(true);
                            }
                        });
                        handler.postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                reconnect(4000);
                            }
                        }, 200);
                    }
                }
            }
        }

        @Override //CALLBACK ON CHARACTERISTIC WRITE (PROPERTY: WHITE)
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {

            if (currentWriteReadFragment != null) {
                currentWriteReadFragment.onActionDataWrite(characteristic.getUuid().toString(), status);
            }

            if (characteristic.getValue().length < 10)
                Log.d("OnCharacteristicRead", "Char: " + characteristic.getUuid().toString() + " Value: " + Converters.getHexValue(characteristic.getValue()) + " Status: " + status);

            if (status != 0) { // Error Handling
                Log.d("onCharWrite", "status: " + Integer.toHexString(status));
                final int error = status;
                if (errorDialog == null) {
                    runOnUiThread(new Runnable() { //Display error on Toast
                        @Override
                        public void run() {
                            errorDialog = new ErrorDialog(error, new ErrorDialog.OtaErrorCallback() {
                                @Override
                                public void onDismiss() {
                                    exit(bluetoothGatt);
                                }
                            });
                            errorDialog.show(getSupportFragmentManager(), "ota_error_dialog");
                        }
                    });
                }
            } else {

                if (characteristic.getUuid().equals(ota_control)) { //OTA Control Callback Handling
                    if (characteristic.getValue().length == 1) {
                        if (characteristic.getValue()[0] == (byte) 0x00) {
                            Log.d("Callback", "Control " + Converters.getHexValue(characteristic.getValue()) + "status: " + status);
                            if (ota_mode && ota_process) {
                                Log.d("OTAUPLOAD", "Sent");
                                runOnUiThread(checkbeginrunnable);
                                handler.removeCallbacks(DFU_OTA_UPLOAD);
                                handler.postDelayed(DFU_OTA_UPLOAD, 500);
                            } else if (!ota_mode && ota_process) {
                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        loadingLog.setText("Resetting...");
                                        showLoading();
                                        animaloading();
                                        Constants.ota_button.setVisible(true);
                                    }
                                });
                                handler.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        reconnect(4000);
                                    }
                                });
                            }
                        }
                        if (characteristic.getValue()[0] == (byte) 0x03) {
                            if (ota_process) {
                                Log.d("Callback", "Control " + Converters.getHexValue(characteristic.getValue()) + "status: " + status);
                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        OTAStart.setBackgroundColor(ContextCompat.getColor(DeviceServicesActivity.this, R.color.silabs_red));
                                        OTAStart.setClickable(true);

                                    }
                                });
                                boolOTAbegin = false;
                                if (boolFullOTA) {
                                    stackPath = "";
                                    runOnUiThread(new Runnable() {
                                        @Override
                                        public void run() {
                                            otaProgress.dismiss();
                                            loadingLog.setText("Loading");
                                            showLoading();
                                            animaloading();
                                        }
                                    });

                                    handler.postDelayed(new Runnable() {
                                        @Override
                                        public void run() {
                                            reconnect(4000);
                                        }
                                    }, 500);
                                }
                            }
                        }
                    } else {
                        Log.i("OTA_Control", "Received: " + Converters.getHexValue(characteristic.getValue()));
                        if (characteristic.getValue()[0] == 0x00 && characteristic.getValue()[1] == 0x02) {
                            Log.i("HomeKit", "Reading OTA_Control...");
                            bluetoothGatt.readCharacteristic(characteristic);
                        }
                    }
                }

                if (characteristic.getUuid().equals(ota_data)) {   //OTA Data Callback Handling
                    if (reliable) {
                        if (otaProgress.isShowing()) {
                            pack += MTU_divisible;
                            if (pack <= otafile.length - 1) {
                                //Log.d("callback", "pack: " + (pack - MTUheader) + " / " + pack + " : " + Converters.getHexValue(characteristic.getValue()));
                                //Log.d("callback", "" + status);
                                otaWriteDataReliable();
                            } else if (pack > otafile.length - 1) {
                                //Log.d("callback", "last: " + pack + " / " + otafile.length + " : " + Converters.getHexValue(characteristic.getValue()));
                                handler.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        runOnUiThread(new Runnable() {
                                            @Override
                                            public void run() {
                                                chrono.stop();
                                                uploadimage.clearAnimation();
                                                uploadimage.setVisibility(View.INVISIBLE);
                                            }
                                        });
                                    }
                                });

                                boolOTAdata = false;
                                DFUMode("OTAEND");

                            }
                        }
                    }
                }
            }
            bluetoothGatt.readCharacteristic(characteristic);
        }

        @Override //CALLBACK ON DESCRIPTOR WRITE
        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            if (currentWriteReadFragment != null) {
                currentWriteReadFragment.onDescriptorWrite(descriptor.getUuid());
            }
        }

        @Override //CALLBACK ON DESCRIPTOR READ
        public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {

            if (descriptor.getUuid().toString().equals(homekit_descriptor.toString())) {

                byte[] value = new byte[2];
                value[0] = (byte) 0xF2;
                value[1] = (byte) 0xFF;

                if (descriptor.getValue()[0] == value[0] && descriptor.getValue()[1] == value[1]) {

                    Log.i("descriptor", "getValue " + Converters.getHexValue(descriptor.getValue()));
                    homeKitOTAControl(descriptor.getValue());

                }
            }
        }

        @Override //CALLBACK ON CHARACTERISTIC CHANGED VALUE (READ - CHARACTERISTIC NOTIFICATION)
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {

            for (int key : characteristicFragments.keySet()) {
                FragmentCharacteristicDetail fragment = characteristicFragments.get(key);
                if (fragment != null && fragment.getmCharact().getUuid().equals(characteristic.getUuid())) {
                    fragment.onActionDataAvailable(characteristic.getUuid().toString());
                    break;
                }
            }

        }

        @Override //CALLBACK ON SERVICES DISCOVERED
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);
//            bluetoothGatt = gatt;

            if (bluetoothGatt != gatt) {
                bluetoothGatt = gatt;
                refreshServices();
            } else {

                discoverTimeout = false;
                /**ERROR IN SERVICE DISCOVERY*/
                if (status != 0) {
                    Log.d("Error status", "" + Integer.toHexString(status));
                    final int error = status;

                    if (errorDialog == null) {
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                errorDialog = new ErrorDialog(error, new ErrorDialog.OtaErrorCallback() {
                                    @Override
                                    public void onDismiss() {
                                        exit(bluetoothGatt);
                                    }
                                });
                                errorDialog.show(getSupportFragmentManager(), "ota_error_dialog");
                            }
                        });
                    }
                } else {
                    /**ON SERVICE DISCOVERY WITHOUT ERROR*/

                    getServicesInfo(gatt); //SHOW SERVICES IN LOG

                    final BluetoothGatt btGatt = gatt;

                    //REFRESH SERVICES UI <- REFRESH SERVICES MENU BUTTON
                    if (boolrefresh_services) {
                        boolrefresh_services = false;
                        handler.postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        onGattFetched();
                                        hideCharacteristicLoadingAnimation();
                                    }
                                });
                            }
                        }, GATT_FETCH_ON_SERVICE_DISCOVERED_DELAY);
                    } else {
                        //DEFINE IF DEVICE SUPPORT OTA & MODE (NORMAL/DFU)

                        final boolean ota_service_check = btGatt.getService(ota_service) != null;
                        if (ota_service_check) {
                            //Log.d("ota_service_check", "" + ota_service_check);
                            final boolean ota_data_check = btGatt.getService(ota_service).getCharacteristic(ota_data) != null;
                            if (ota_data_check) {
                                //Log.d("ota_data_check", "" + ota_data_check);
                                final boolean homekit_check = btGatt.getService(homekit_service) != null;
                                if (!homekit_check) {
                                    ota_mode = true;
                                    int ota_data_property = btGatt.getService(ota_service).getCharacteristic(ota_data).getProperties();
                                    if (ota_data_property == 12 || ota_data_property == 8 || ota_data_property == 10) {
                                        //reliable = true;
                                    } else if (ota_mode && ota_data_property == 4) {
                                        //reliable = false;
                                    }
                                }
                            } else {
                                if (boolOTAbegin) onceAgain();
                            }
                        } //else Log.d("ota_service_check", "" + ota_service_check);


                        //REQUEST MTU
                        if (UICreated && loadingdialog.isShowing()) {
                            bluetoothGatt.requestMtu(MTU);
                        }

                        //LAUNCH SERVICES UI
                        if (!boolFullOTA) {
                            handler.postDelayed(new Runnable() {
                                @Override
                                public void run() {
                                    runOnUiThread(new Runnable() {
                                        @Override
                                        public void run() {
                                            onGattFetched();
                                            hideCharacteristicLoadingAnimation();
                                        }
                                    });
                                }
                            }, GATT_FETCH_ON_SERVICE_DISCOVERED_DELAY);
                        }

                        //IF DFU_MODE, LAUNCH OTA SETUP AUTOMATICALLY
                        if (ota_mode && boolOTAbegin) {
                            handler.postDelayed(new Runnable() {
                                @Override
                                public void run() {
                                    runOnUiThread(new Runnable() {
                                        @Override
                                        public void run() {
                                            loadingimage.setVisibility(View.GONE);
                                            loadingdialog.dismiss();
                                            showOtaProgress();
                                        }
                                    });
                                }
                            }, (int) (2.5 * UI_CREATION_DELAY));
                        }
                    }
                }
            }
        }
    };
    /************************************************************************************/

    /**
     * BLUETOOTH ADAPTER RESPONSES
     **************************************************/
    private final BroadcastReceiver bluetoothAdapterStateChangeListener = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();
            final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
                    BluetoothAdapter.ERROR);

            if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
                switch (state) {
                    case BluetoothAdapter.STATE_ON:
                        //Log.d("BTAdapter","STATE_ON");
                        break;
                    case BluetoothAdapter.STATE_OFF:
                        //Log.d("BTAdapter","STATE_OFF");
                        break;
                }
            }
        }
    };
    /*************************************************************************************/


    /**
     * ATTENTION: This was auto-generated to implement the App Indexing API.
     * See https://g.co/AppIndexing/AndroidStudio for more information.
     */
    private GoogleApiClient client;

    /**
     * ACTIVITY STATES MACHINE
     ***********************************************************/

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_device_services);
        ButterKnife.inject(this);

        setSupportActionBar(toolbar);

        findViewById(R.id.go_back_button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onBackPressed();
            }
        });


        if (!getResources().getBoolean(R.bool.isTablet)) {
            rssiTV.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);
            int size = (int) (getResources().getDisplayMetrics().density * 16);
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(size, size);
            rssiIV.setLayoutParams(params);
        }

        sharedPrefUtils = new SharedPrefUtils(DeviceServicesActivity.this);
        characteristicNamesMap = sharedPrefUtils.getCharacteristicNamesMap();
        serviceNamesMap = sharedPrefUtils.getServiceNamesMap();


        if (savedInstanceState == null) {
            LogFragmentConnected logFragment = new LogFragmentConnected();
            androidx.fragment.app.FragmentTransaction logtransaction = getSupportFragmentManager().beginTransaction();
            logtransaction.add(R.id.log_body_connected, logFragment);
            logtransaction.commit();
        }

        reScanCallback = new ScanCallback() {
            @Override
            public void onScanResult(int callbackType, ScanResult result) {
                final BluetoothDevice btDevice = result.getDevice();
                onScanCallback++;
                loadingLog.setText(getResources().getText(R.string.Waiting_to_connect));
                reconnectGatt(btDevice);
                onScanCallback = 0;
            }
        };

        initDevice(getDeviceAddress(savedInstanceState));

        IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
        registerReceiver(bluetoothAdapterStateChangeListener, filter);

        // ATTENTION: This was auto-generated to implement the App Indexing API.
        // See https://g.co/AppIndexing/AndroidStudio for more information.
        client = new GoogleApiClient.Builder(this).addApi(AppIndex.API).build();

        fragmentsInit();

        setToolbarItemsNotClicked();

        connectionsLL.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (btToolbarOpened && btToolbarOpenedName == ToolbarName.CONNECTIONS) {
                    closeToolbar();
                    btToolbarOpened = !btToolbarOpened;
                    return;
                }
                if (!btToolbarOpened) {
                    bluetoothBrowserBackgroundRL.setBackgroundColor(Color.parseColor("#99000000"));
                    bluetoothBrowserBackgroundRL.setVisibility(View.VISIBLE);
                    ViewCompat.setTranslationZ(bluetoothBrowserBackgroundRL, 4f);
                    animateToolbarOpen(TOOLBAR_OPEN_PERCENTAGE, 300);
                    btToolbarOpened = !btToolbarOpened;
                }
                setToolbarItemsNotClicked();
                setToolbarItemClicked(connectionsIV, connectionsTV);
                btToolbarOpenedName = ToolbarName.CONNECTIONS;
                setToolbarFragment(connectionsFragment);
            }
        });

        logLL.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (btToolbarOpened && btToolbarOpenedName == ToolbarName.LOGS) {
                    loggerFragment.stopLogUpdater();
                    closeToolbar();
                    btToolbarOpened = !btToolbarOpened;
                    return;
                }
                if (!btToolbarOpened) {
                    bluetoothBrowserBackgroundRL.setBackgroundColor(Color.parseColor("#99000000"));
                    bluetoothBrowserBackgroundRL.setVisibility(View.VISIBLE);
                    ViewCompat.setTranslationZ(bluetoothBrowserBackgroundRL, 4f);
                    animateToolbarOpen(TOOLBAR_OPEN_PERCENTAGE, 300);
                    btToolbarOpened = !btToolbarOpened;
                }
                setToolbarItemsNotClicked();
                setToolbarItemClicked(logIV, logTV);
                btToolbarOpenedName = ToolbarName.LOGS;
                setToolbarFragment(loggerFragment);
                loggerFragment.scrollToEnd();
                loggerFragment.runLogUpdater();
            }
        });
    }

    private String getDeviceAddress(final Bundle savedInstanceState) {
        String deviceAddress;
        if (savedInstanceState == null) {
            Bundle extras = getIntent().getExtras();
            if (extras == null) {
                deviceAddress = null;
            } else {
                deviceAddress = extras.getString("DEVICE_SELECTED_ADDRESS");
            }
        } else {
            deviceAddress = savedInstanceState.getString("DEVICE_SELECTED_ADDRESS");
        }
        this.deviceAddress = deviceAddress;
        return deviceAddress;
    }

    @Override
    protected void onSaveInstanceState(@NonNull Bundle outState) {
        outState.putString("DEVICE_SELECTED_ADDRESS", deviceAddress);
        super.onSaveInstanceState(outState);
    }

    @Override
    protected void onResume() {
        super.onResume();
        if ((serviceHasBeenSet && service == null) || (service != null && !service.isGattConnected())) {
            Toast.makeText(DeviceServicesActivity.this, R.string.toast_debug_connection_failed, Toast.LENGTH_LONG).show();
            if (bluetoothGatt != null) if (service != null) {
                service.clearGatt();
            }
            bluetoothBinding.unbind();
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        sharedPrefUtils.saveCharacteristicNamesMap(characteristicNamesMap);
        sharedPrefUtils.saveServiceNamesMap(serviceNamesMap);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        if(otaProgress != null) otaProgress.dismiss();
        if(loadingdialog != null) loadingdialog.dismiss();

        try {
            unregisterReceiver(bluetoothAdapterStateChangeListener);
        } catch (Exception b) {
            Log.e("onDestroy", "unregisterReceiver Err" + b);
        }

        // zakomentowane, aby nie rozłączać
//        if (service != null) {
//            //Log.d("onDestroy","called");
//            service.clearGatt();
//        }

        try {
            bluetoothBinding.unbind();
        } catch (Exception e) {
            Log.e("onDestroy", "bluetoothBinding Err" + e);
        }

//        if (bluetoothGatt != null) bluetoothGatt = null;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_device_services_bluegiga, menu);
        Constants.ota_button = menu.findItem(R.id.OTA_button);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.menu_item_license:
                showAboutDialog();
                break;
            case R.id.menu_log: //LOG MENU BUTTON
                adjustLayout();
                break;
            case R.id.OTA_button: //OTA MENU BUTTON
                if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                    ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, WRITE_EXTERNAL_STORAGE_REQUEST_PERMISSION);
                } else if (UICreated) {
                    otaMode = true;
                    OTAonClick();
                }
                break;
            case R.id.refresh_services: //REFRESH SERVICES MENU BUTTON
                boolrefresh_services = true;
                refreshServices();
                break;
            case R.id.request_mtu: //REQUEST MTU MENU BUTTON
                if (UICreated) {
                    boolrequest_mtu = true;
                    showRequestMTU();
                }
                break;
            case R.id.request_priority://REQUEST PRIORITY MENU BUTTON
                if (bluetoothGatt != null && newPriority != null) {
                    showRequestPriority();
                }
                break;
            case android.R.id.home: //BACK MENU BUTTON
                onBackPressed();
                return true;
            default:
                break;
        }

        return super.onOptionsItemSelected(item);
    }


    /*****************************************************************************************/

    /**
     * FUNCTIONS
     *****************************************************************************/


    public void onceAgain() {
        writeOtaControl((byte) 0x00);
    }

    /**
     * START OTA BUTTON (UI, Bools)
     */
    public void OTAonClick() {

        if (ota_mode) {
            ota_process = true;
            boolOTAbegin = false;
        } else {
            ota_process = true;
            boolOTAbegin = true;
        }

        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                loadingimage.setVisibility(View.GONE);
                loadingdialog.dismiss();
                showOtaSetup();
                Constants.ota_button.setVisible(true);
            }
        });
    }

    /**
     * ACTION WHEN MTU MENU BUTTON IS PRESSED
     ************************************************/
    public void MTUonButtonMenu() {
        boolrequest_mtu = false;
        if (newMTU.isShowing()) {
            newMTU.dismiss();
        }
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(getApplicationContext(), getResources().getString(R.string.MTU_colon_n, MTU), Toast.LENGTH_LONG).show();
            }
        });
    }

    /**
     * CLOSES THE MTU DIALOG AND SHOW OTA SETUP DIALOG
     *****************************************/
    public void reopenOTASetup() {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                newMTU.dismiss();
                otaSetup.show();
                requestMTU.setProgress(MTU);
            }
        });

    }

    /**
     * SETS ALL THE INFO IN THE OTA PROGRESS DIALOG TO "" OR 0
     ********************************/
    public void resetOTAProgress() {
        boolFullOTA = false;
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                datasize.setText("");
                filename.setText("");
                loadingimage.setVisibility(View.GONE);
                loadingdialog.dismiss();
                progressBar.setProgress(0);
                datasize.setText(getResources().getString(R.string.zero_percent));
                dataRate.setText("");
                OTAStart.setClickable(false);
                OTAStart.setBackgroundColor(ContextCompat.getColor(DeviceServicesActivity.this, R.color.silabs_button_inactive));
                showOtaProgress();
            }
        });
    }

    /**
     * FUTURE IMPLEMENTATION OF LOG VIEW
     **************************************************/
    //Not used
    private void adjustLayout() { //TODO Fix animation (Almost ok)
        float scale = getResources().getDisplayMetrics().density;
        RelativeLayout logbody = findViewById(R.id.log_body_connected);
        ViewGroup.MarginLayoutParams svw = (ViewGroup.MarginLayoutParams) scrollViewWrapper.getLayoutParams();
        if (logbody.getVisibility() == View.GONE) {
            Log.i("adjustLayout", "Creating View");
            running = true;
            svw.setMargins(0, 0, 0, (int) ((scale * 300) + 0.5f));
            scrollViewWrapper.setLayoutParams(svw);
            logbody.setVisibility(View.VISIBLE);
            startlog();
        } else {
            Log.i("adjustLayout", "Hiding View");
            running = false;
            svw.setMargins(0, 0, 0, 0);
            scrollViewWrapper.setLayoutParams(svw);
            logbody.setVisibility(View.GONE);
        }
    }

    //Not used
    private void startlog() {
        if (logupdate == null) {
            logupdate = new Thread(new Runnable() {
                public void run() {
                    synchronized (this) {
                        try {
                            while (running) {
                                log();
                                Thread.sleep(250);
                            }
                        } catch (Exception e) {
                        }
                    }
                }
            });
            logupdate.start();
        }
    }

    //Not used
    private void log() {
        StringBuilder stringBuilder = new StringBuilder();

        try {
            Process process = Runtime.getRuntime().exec("logcat -d");
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;

            while ((line = bufferedReader.readLine()) != null) {
                if (line.contains(" I ") && !line.contains("ViewRoot") && !line.contains("readRssi()")) { //TODO Log filter options
                    stringBuilder.append(line);
                    stringBuilder.append("\n");
                }
            }
            if (substraction.toString().length() != stringBuilder.toString().length()) {
                String result;
                if (substraction.toString().length() > stringBuilder.toString().length())
                    result = stringBuilder.toString();
                else
                    result = stringBuilder.substring(substraction.length(), stringBuilder.length());
                Message m = new Message();
                Bundle b = new Bundle();
                b.putString("whatisconnected", result);
                m.setData(b);
                logHandler.sendMessage(m);
                substraction = stringBuilder;
            }
        } catch (IOException e) {
            Log.e("log()", "couldn't create log");
        }
    }

    //Not used
    public Handler logHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            //Log.i("handler","receiving message");
            String sentString = msg.getData().getString("whatisconnected");
            try {
                tv = findViewById(R.id.log_view);
                tv.setMovementMethod(new ScrollingMovementMethod());
                tv.append(sentString);
                tv.scrollTo(0, tv.getScrollY());
            } catch (Exception e) {
                Log.e("mHandle", "error: " + e);
            }

        }
    };

    //Not used
    public String getdate(String string) {
        String currentDateTimeString = null;
        switch (string) {
            case "normal":
                currentDateTimeString = DateFormat.getDateTimeInstance().format(new Date());
                break;
            case "compact":
                SimpleDateFormat LOG_FILE_FORMAT = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ssZ");
                currentDateTimeString = LOG_FILE_FORMAT.format(new Date());
                break;
        }
        return currentDateTimeString;
    }

    //Not used
    public File save_log() {
        final File path = new File(Environment.getExternalStorageDirectory(), "SiliconLabs_EFRConnect");
        final File file = new File(path + File.separator + "SiliconLabs." + getdate("compact") + ".txt");
        Thread save_log = new Thread(new Runnable() {
            public void run() {
                String savedata = tv.getText().toString();
                if (!path.exists()) path.mkdirs();
                BufferedWriter bw = null;
                try {
                    file.createNewFile();
                    bw = new BufferedWriter(new FileWriter(file), 1024);
                    bw.write(savedata);
                } catch (IOException e) {
                    Log.e("save_log()", "error saving log", e);
                } finally {
                    if (bw != null) {
                        try {
                            bw.close();
                        } catch (IOException e) {
                            Log.e("save_log()", "error closing save_log()", e);
                        }
                    }
                }
            }
        });
        save_log.start();
        return file;
    }

    //Not used
    public void share_log() {
        Thread share_log = new Thread(new Runnable() {
            public void run() {
                String logdata = tv.getText().toString();
                Intent shareIntent = new Intent(Intent.ACTION_SEND);
                shareIntent.setType("text/plain");
                shareIntent.putExtra(Intent.EXTRA_SUBJECT, "SiliconLabs BGApp Log: " + getdate("normal"));
                shareIntent.putExtra(Intent.EXTRA_TEXT, logdata);
                startActivity(Intent.createChooser(shareIntent, "Share SiliconLabs BGApp Log ..."));
            }
        });
        share_log.start();
    }

    //Not used
    public void clear_log() {
        tv = findViewById(R.id.log_view);
        tv.setText("");
        try {
            Runtime.getRuntime().exec(new String[]{"logcat", "-c"});
            Log.i("clear_log()", "log cleaned");
            //restartlog();
        } catch (IOException e) {
            Log.e("alogcat", "error clearing log", e);
        }
    }
    /**************************************************************************************/


    /**
     * USED TO CLEAN CACHE AND REDISCOVER SERVICES
     ****************************************/
    private void refreshServices() {
        if (bluetoothGatt != null && bluetoothGatt.getDevice() != null) {
            refreshDeviceCache(bluetoothGatt);
            bluetoothGatt.discoverServices();
        } else if (service != null && service.getConnectedGatt() != null) {
            refreshDeviceCache(service.getConnectedGatt());
            service.getConnectedGatt().discoverServices();
        }
    }

    /**
     * INITIATES SERVICES VIEWS
     ************************************************************/
    private void initServicesViews() {
        serviceItemContainers = new HashMap<>();
        // iterate through all of the services for the device, inflate and add views to the scrollview
        ArrayList<BluetoothGattService> services = (ArrayList<BluetoothGattService>) bluetoothGatt.getServices(); //service.getConnectedGatt().getServices();
        for (int position = 0; position < services.size(); position++) {
            final ServiceItemContainer serviceItemContainer = new ServiceItemContainer(DeviceServicesActivity.this);

            // get information about service at index 'position'
            UUID uuid = services.get(position).getUuid();
            Service service = Engine.getInstance().getService(uuid);
            String serviceName = Common.getServiceName(uuid, getApplicationContext());
            String serviceUuid = Common.getUuidText(uuid);

            serviceName = Common.checkOTAService(serviceUuid, serviceName);

            // initialize information about services in service item container
            initServiceItemContainer(serviceItemContainer, position, serviceName, serviceUuid);

            // initialize views for each characteristic of the service, put into characteristics expansion for service's list item
            final BluetoothGattService blueToothGattService = service == null ? services.get(position) : bluetoothGatt.getService(service.getUuid());
            List<BluetoothGattCharacteristic> characteristics = blueToothGattService.getCharacteristics();
            if (characteristics.size() == 0) {
                serviceItemContainer.serviceInfoCardView.setBackgroundColor(Color.LTGRAY);
                continue;
            }
            // iterate through the characteristics of this service
            for (final BluetoothGattCharacteristic bluetoothGattCharacteristic : characteristics) {
                // retrieve relevant bluetooth data for characteristic of service
                final BluetoothGattCharacteristic thisCharacteristic = bluetoothGattCharacteristic;
                // the engine parses through the data of the btgattcharac and returns a wrapper characteristic
                // the wrapper characteristic is matched with accepted bt gatt profiles, provides field types/values/units
                Characteristic charact = Engine.getInstance().getCharacteristic(bluetoothGattCharacteristic.getUuid());
                String characteristicName;

                if (charact != null) {
                    characteristicName = charact.getName().trim();
                } else {
                    characteristicName = getOtaSpecificCharacteristicName(bluetoothGattCharacteristic.getUuid().toString());
                }


                final String characteristicUuid = (charact != null ? Common.getUuidText(charact.getUuid()) : Common.getUuidText(bluetoothGattCharacteristic.getUuid()));

                //TODO: They are in GattCharacteristic, but their names are not appearing
                if (characteristicUuid.equals(ota_control.toString()))
                    characteristicName = "OTA Control";
                if (characteristicUuid.equals(ota_data.toString())) characteristicName = "OTA Data";
                if (characteristicUuid.equals(fw_version.toString()))
                    characteristicName = "FW Version";
                if (characteristicUuid.equals(ota_version.toString()))
                    characteristicName = "OTA Version";

                // inflate/create ui elements
                LayoutInflater layoutInflater = LayoutInflater.from(this);
                final LinearLayout characteristicContainer = (LinearLayout) layoutInflater.inflate(R.layout.list_item_debug_mode_characteristic_of_service, null);
                final LinearLayout characteristicExpansion = characteristicContainer.findViewById(R.id.characteristic_expansion);
                final LinearLayout propsContainer = characteristicContainer.findViewById(R.id.characteristic_props_container);
                final TextView characteristicNameTextView = characteristicContainer.findViewById(R.id.characteristic_title);
                final TextView characteristicUuidTextView = characteristicContainer.findViewById(R.id.characteristic_uuid);
                final TextView descriptorsLabelTextView = characteristicContainer.findViewById(R.id.text_view_descriptors_label);
                final LinearLayout descriptorLinearLayout = characteristicContainer.findViewById(R.id.linear_layout_descriptor);
                final ImageView characteristicEditNameImageView = characteristicContainer.findViewById(R.id.image_view_edit_charac_name);
                final LinearLayout characEditNameLinearLayout = characteristicContainer.findViewById(R.id.linear_layout_edit_charac_name);
                final LinearLayout showCharacDetailsLinearLayout = characteristicContainer.findViewById(R.id.linear_layout_charac_details);
                View characteristicSeparator = characteristicContainer.findViewById(R.id.characteristics_separator);
                final int id = generateNextId();
                characteristicExpansion.setId(id);

                loadCharacteristicDescriptors(bluetoothGattCharacteristic, descriptorsLabelTextView, descriptorLinearLayout);

                // init/populate ui elements with info from bluetooth data for characteristic of service
                characteristicNameTextView.setText(characteristicName);

                if (characteristicName.equals(getString(R.string.unknown_characteristic_label))) {
                    characteristicEditNameImageView.setVisibility(View.VISIBLE);

                    characEditNameLinearLayout.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            Dialog dialog = new MappingsEditDialog(DeviceServicesActivity.this,
                                    characteristicNameTextView.getText().toString(),
                                    characteristicUuid,
                                    new MappingCallback() {
                                        @Override
                                        public void onNameChanged(Mapping nameMapping) {
                                            characteristicNameTextView.setText(nameMapping.getName());
                                            characteristicNamesMap.put(nameMapping.getUuid(), nameMapping);
                                        }
                                    }, MappingType.CHARACTERISTIC);
                            dialog.show();
                        }
                    });

                    if (characteristicNamesMap.containsKey(characteristicUuid)) {
                        characteristicNameTextView.setText(characteristicNamesMap.get(characteristicUuid).getName());
                    }


                }


                characteristicUuidTextView.setText(characteristicUuid);

                // hide divider between characteristics if last characteristic of service
                if (serviceItemContainer.groupOfCharacteristicsForService.getChildCount() == characteristics.size() - 1) {
                    characteristicSeparator.setVisibility(View.GONE);
                    serviceItemContainer.lastItemDivider.setVisibility(View.VISIBLE);
                }
                serviceItemContainer.groupOfCharacteristicsForService.addView(characteristicContainer);

                final String finalServiceName = serviceName;

                // add properties to characteristic list item in expansion
                addPropertiesToCharacteristic(bluetoothGattCharacteristic, propsContainer);
                setPropertyClickListeners(propsContainer, bluetoothGattCharacteristic, blueToothGattService, finalServiceName, characteristicExpansion);
                serviceItemContainer.setCharacteristicNotificationState(characteristicUuid, Notifications.DISABLED);

                characteristicContainer.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if (characteristicExpansion.getVisibility() == View.VISIBLE) {
                            characteristicExpansion.setVisibility(View.GONE);
                        } else {
                            characteristicExpansion.setVisibility(View.VISIBLE);

                            if (characteristicFragments.containsKey(id)) {
                                currentWriteReadFragment = characteristicFragments.get(id);
                            } else {
                                currentWriteReadFragment = initFragmentCharacteristicDetail(bluetoothGattCharacteristic, id, blueToothGattService, characteristicExpansion, false);
                                characteristicFragments.put(id, currentWriteReadFragment);
                            }
                        }
                    }
                });


            }

            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);

            int margin16Dp = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, getResources().getDisplayMetrics());
            int margin10Dp = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, getResources().getDisplayMetrics());

            if (position == 0) {
                params.setMargins(margin16Dp, margin16Dp, margin16Dp, margin10Dp);
            } else if (position == services.size() - 1) {
                params.setMargins(margin16Dp, margin10Dp, margin16Dp, margin16Dp);
            } else {
                params.setMargins(margin16Dp, margin10Dp, margin16Dp, margin10Dp);
            }

            serviceItemContainer.serviceInfoCardView.setLayoutParams(params);
            servicesContainer.addView(serviceItemContainer, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
            serviceItemContainers.put(serviceName, serviceItemContainer);
        }
    }

    private void loadCharacteristicDescriptors(BluetoothGattCharacteristic bluetoothGattCharacteristic, TextView descriptorsLabelTextView, LinearLayout descriptorLinearLayout) {
        if (bluetoothGattCharacteristic.getDescriptors().size() <= 0) {
            descriptorsLabelTextView.setVisibility(View.GONE);
        } else {
            for (BluetoothGattDescriptor d : bluetoothGattCharacteristic.getDescriptors()) {
                Descriptor descriptor = Engine.getInstance().getDescriptorByUUID(d.getUuid());

                TextView descriptorNameTV = new TextView(this);
                descriptorNameTV.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
                if (descriptor == null) {
                    descriptorNameTV.setText(getResources().getString(R.string.unknown));
                } else {
                    descriptorNameTV.setText(descriptor.getName());
                }
                descriptorNameTV.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);
                descriptorNameTV.setTextColor(ContextCompat.getColor(this, R.color.silabs_primary_text));
                descriptorLinearLayout.addView(descriptorNameTV);


                LinearLayout uuidLL = new LinearLayout(this);
                uuidLL.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
                uuidLL.setOrientation(LinearLayout.HORIZONTAL);

                TextView uuidLabelTV = new TextView(this);
                LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                params.setMarginEnd((int) (getResources().getDisplayMetrics().density * 4));
                uuidLabelTV.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
                uuidLabelTV.setText(getResources().getText(R.string.UUID_colon));
                uuidLabelTV.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);
                uuidLabelTV.setLayoutParams(params);
                uuidLL.addView(uuidLabelTV);

                TextView uuidTV = new TextView(this);
                uuidTV.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
                if (descriptor != null) {
                    uuidTV.setText(Common.getUuidText(descriptor.getUuid()));
                } else {
                    uuidTV.setText(getResources().getString(R.string.unknown));
                }

                uuidTV.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);
                uuidTV.setTextColor(ContextCompat.getColor(this, R.color.silabs_primary_text));
                uuidLL.addView(uuidTV);

                descriptorLinearLayout.addView(uuidLL);
            }
        }

    }

    // This is used to refresh from FragmentCharacteristicDetail after a write
    // refactor into a callback / a more comprehensive mechanism needed
    Button btnCaretPressed = null;

    public void refreshCharacteristicExpansion() {
        if (btnCaretPressed != null) {
            btnCaretPressed.performClick();
            btnCaretPressed.performClick();
        }
    }

    /**
     * INITIATES SERVICES ITENS
     ************************************************************/
    private void initServiceItemContainer(final ServiceItemContainer serviceItemContainer, int position, String serviceName, String serviceUuid) {

        if (position == 0) {
            UICreated = true;
            if (bluetoothGatt.getServices().contains(bluetoothGatt.getService(ota_service))) {
                Constants.ota_button.setVisible(true);
            } else {
                Constants.ota_button.setVisible(false);
            }
        }
        serviceItemContainer.groupOfCharacteristicsForService.setVisibility(View.GONE);
        serviceItemContainer.groupOfCharacteristicsForService.removeAllViews();
        serviceItemContainer.serviceTitleTextView.setText(serviceName);
        serviceItemContainer.serviceUuidTextView.setText(serviceUuid);


        if (serviceName.equals(getString(R.string.unknown_service))) {
            serviceItemContainer.serviceEditNameImageView.setVisibility(View.VISIBLE);
            serviceItemContainer.serviceEditNameLinearLayout.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Dialog dialog = new MappingsEditDialog(DeviceServicesActivity.this,
                            serviceItemContainer.serviceTitleTextView.getText().toString(),
                            serviceItemContainer.serviceUuidTextView.getText().toString(),
                            new MappingCallback() {
                                @Override
                                public void onNameChanged(Mapping nameMapping) {
                                    serviceItemContainer.serviceTitleTextView.setText(nameMapping.getName());
                                    serviceNamesMap.put(nameMapping.getUuid(), nameMapping);
                                }
                            }, MappingType.SERVICE);
                    dialog.show();
                }
            });

            if (serviceNamesMap.containsKey(serviceUuid)) {
                serviceItemContainer.serviceTitleTextView.setText(serviceNamesMap.get(serviceUuid).getName());
            }
        }

    }

    /**
     * SHOW CHARACTERISTIC PROPERTIES IN UI: READ, WRITE
     ************************************/
    private void addPropertiesToCharacteristic(BluetoothGattCharacteristic bluetoothGattCharacteristic,
                                               LinearLayout propsContainer) {
        String propertiesString = Common.getProperties(DeviceServicesActivity.this, bluetoothGattCharacteristic.getProperties());
        String[] propsExploded = propertiesString.split(",");


        if (Arrays.toString(propsExploded).toLowerCase().contains("write no response")) {
            ArrayList<String> temp = new ArrayList<>();
            boolean writeAdded = false;

            for (String s : propsExploded) {
                if (s.toLowerCase().contains("write no response") && !writeAdded) {
                    temp.add("Write");
                    writeAdded = true;
                } else if (!s.toLowerCase().contains("write")) {
                    temp.add(s);
                }
            }

            propsExploded = new String[temp.size()];

            for (int i = 0; i < temp.size(); i++) {
                propsExploded[i] = temp.get(i);
            }
        }

        for (String propertyValue : propsExploded) {
            TextView propertyView = new TextView(this);

            String propertyValueTrimmed = propertyValue.trim();
            propertyValueTrimmed = propertyValue.length() > 13 ? propertyValue.substring(0, 13) : propertyValueTrimmed;
            propertyView.setText(propertyValueTrimmed);
            propertyView.setBackgroundColor(ContextCompat.getColor(DeviceServicesActivity.this, R.color.silabs_white));
            propertyView.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(R.dimen.characteristic_property_text_size));
            propertyView.setTextColor(ContextCompat.getColor(DeviceServicesActivity.this, R.color.silabs_inactive));
            propertyView.setTypeface(Typeface.DEFAULT_BOLD);
            propertyView.setGravity(Gravity.CENTER_VERTICAL);

            LinearLayout propertyContainer = new LinearLayout(DeviceServicesActivity.this);
            propertyContainer.setOrientation(LinearLayout.HORIZONTAL);

            ImageView propertyIcon = new ImageView(DeviceServicesActivity.this);
            int iconId;
            if (propertyValue.trim().toUpperCase().equals(Common.PROPERTY_VALUE_BROADCAST)) {
                iconId = R.drawable.debug_prop_broadcast;
            } else if (propertyValue.trim().toUpperCase().equals(Common.PROPERTY_VALUE_READ)) {
                iconId = R.drawable.ic_icon_read_off;
            } else if (propertyValue.trim().toUpperCase().equals(Common.PROPERTY_VALUE_WRITE)) {
                iconId = R.drawable.ic_icon_edit_off;
            } else if (propertyValue.trim().toUpperCase().equals(Common.PROPERTY_VALUE_NOTIFY)) {
                iconId = R.drawable.ic_icon_notify_off;
            } else if (propertyValue.trim().toUpperCase().equals(Common.PROPERTY_VALUE_INDICATE)) {
                iconId = R.drawable.ic_icon_indicate_off;
            } else if (propertyValue.trim().toUpperCase().equals(Common.PROPERTY_VALUE_SIGNED_WRITE)) {
                iconId = R.drawable.debug_prop_signed_write;
            } else if (propertyValue.trim().toUpperCase().equals(Common.PROPERTY_VALUE_EXTENDED_PROPS)) {
                iconId = R.drawable.debug_prop_ext;
            } else {
                iconId = R.drawable.debug_prop_ext;
            }
            propertyIcon.setBackgroundResource(iconId);
            propertyIcon.setTag(PROPERTY_ICON_TAG);
            propertyView.setTag(PROPERTY_NAME_TAG);

            LinearLayout.LayoutParams paramsText = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
            paramsText.gravity = Gravity.CENTER_VERTICAL;

            //int propIconEdgeLength = getResources().getDimensionPixelSize(R.dimen.prop_icon_edge_length);
            LinearLayout.LayoutParams paramsIcon = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            float d = getResources().getDisplayMetrics().density;
            paramsIcon.setMarginEnd((int) (8 * d));
            paramsIcon.gravity = Gravity.CENTER_VERTICAL;

            propertyContainer.addView(propertyIcon, paramsIcon);
            propertyContainer.addView(propertyView, paramsText);

            propertyContainer.setTag(propertyValue);

            LinearLayout.LayoutParams paramsTextAndIconContainer = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT);
            paramsTextAndIconContainer.setMargins(0, (int) (4 * d), (int) (10 * d), 0);
            propertyContainer.setPadding((int) (2 * d), (int) (8 * d), (int) (6 * d), (int) (6 * d));
            propsContainer.addView(propertyContainer, paramsTextAndIconContainer);
        }
    }

    private void setPropertyClickListeners(LinearLayout propsContainer, final BluetoothGattCharacteristic bluetoothGattCharacteristic, final BluetoothGattService service, final String serviceName, final LinearLayout characteristicExpansion) {
        final ImageView notificationIcon = getIconWithValue(propsContainer, Common.PROPERTY_VALUE_NOTIFY);
        final TextView notificationText = getTextViewWithValue(propsContainer, Common.PROPERTY_VALUE_NOTIFY);
        final ImageView indicationIcon = getIconWithValue(propsContainer, Common.PROPERTY_VALUE_INDICATE);
        final TextView indicationText = getTextViewWithValue(propsContainer, Common.PROPERTY_VALUE_INDICATE);
        final ImageView readIcon = getIconWithValue(propsContainer, Common.PROPERTY_VALUE_READ);
        final TextView readText = getTextViewWithValue(propsContainer, Common.PROPERTY_VALUE_READ);
        final ImageView writeIcon = getIconWithValue(propsContainer, Common.PROPERTY_VALUE_WRITE);
        final TextView writeText = getTextViewWithValue(propsContainer, Common.PROPERTY_VALUE_WRITE);
        final int id = characteristicExpansion.getId();

        for (int i = 0; i < propsContainer.getChildCount(); i++) {
            if (propsContainer.getChildAt(i).getTag() == null) {
                continue;
            }
            final LinearLayout propertyContainer = (LinearLayout) propsContainer.getChildAt(i);
            String propertyValueId = ((String) propertyContainer.getTag()).trim().toUpperCase();
            switch (propertyValueId) {
                case Common.PROPERTY_VALUE_READ:
                    propertyContainer.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            readIcon.startAnimation(AnimationUtils.loadAnimation(DeviceServicesActivity.this, R.anim.property_image_click));

                            if (characteristicFragments.containsKey(id)) {
                                currentWriteReadFragment = characteristicFragments.get(id);
                            } else {
                                currentWriteReadFragment = initFragmentCharacteristicDetail(bluetoothGattCharacteristic, id, service, characteristicExpansion, false);
                                characteristicFragments.put(id, currentWriteReadFragment);
                            }

                            characteristicExpansion.setVisibility(View.VISIBLE);

                            bluetoothGatt.readCharacteristic(bluetoothGattCharacteristic);

                        }
                    });

                    break;
                case Common.PROPERTY_VALUE_WRITE:
                    propertyContainer.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            writeIcon.startAnimation(AnimationUtils.loadAnimation(DeviceServicesActivity.this, R.anim.property_image_click));

                            if (characteristicFragments.containsKey(id)) {
                                currentWriteReadFragment = characteristicFragments.get(id);
                                characteristicFragments.get(id).showCharacteristicWriteDialog();
                            } else {
                                currentWriteReadFragment = initFragmentCharacteristicDetail(bluetoothGattCharacteristic, id, service, characteristicExpansion, true);
                                characteristicFragments.put(id, currentWriteReadFragment);
                            }

                            characteristicExpansion.setVisibility(View.VISIBLE);
                        }
                    });

                    break;
                case Common.PROPERTY_VALUE_NOTIFY:
                    propertyContainer.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            notificationIcon.startAnimation(AnimationUtils.loadAnimation(DeviceServicesActivity.this, R.anim.property_image_click));

                            if (characteristicFragments.containsKey(id)) {
                                currentWriteReadFragment = characteristicFragments.get(id);

                                if (characteristicExpansion.getVisibility() == View.GONE && notificationText.getCurrentTextColor() == ContextCompat.getColor(DeviceServicesActivity.this, R.color.silabs_inactive)) {
                                    characteristicExpansion.setVisibility(View.VISIBLE);
                                }
                            } else {
                                currentWriteReadFragment = initFragmentCharacteristicDetail(bluetoothGattCharacteristic, id, service, characteristicExpansion, false);
                                characteristicFragments.put(id, currentWriteReadFragment);
                            }
                            setNotifyProperty(bluetoothGattCharacteristic, serviceName, notificationIcon, notificationText, indicationIcon, indicationText);
                        }
                    });

                    break;
                case Common.PROPERTY_VALUE_INDICATE:
                    propertyContainer.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            indicationIcon.startAnimation(AnimationUtils.loadAnimation(DeviceServicesActivity.this, R.anim.property_image_click));

                            if (characteristicFragments.containsKey(id)) {
                                currentWriteReadFragment = characteristicFragments.get(id);

                                if (characteristicExpansion.getVisibility() == View.GONE && indicationText.getCurrentTextColor() == ContextCompat.getColor(DeviceServicesActivity.this, R.color.silabs_inactive)) {
                                    characteristicExpansion.setVisibility(View.VISIBLE);
                                }
                            } else {
                                currentWriteReadFragment = initFragmentCharacteristicDetail(bluetoothGattCharacteristic, id, service, characteristicExpansion, false);
                                characteristicFragments.put(id, currentWriteReadFragment);
                            }
                            setIndicateProperty(bluetoothGattCharacteristic, serviceName, indicationIcon, indicationText, notificationIcon, notificationText);
                        }
                    });
                    break;
                default:
                    break;
            }
        }
    }

    private FragmentCharacteristicDetail initFragmentCharacteristicDetail(BluetoothGattCharacteristic bluetoothGattCharacteristic, int expansionId, BluetoothGattService service, LinearLayout characteristicExpansion, boolean displayWriteDialog) {
        FragmentManager fragmentManager = getFragmentManager();

        FragmentCharacteristicDetail characteristicDetail = new FragmentCharacteristicDetail();
        characteristicDetail.address = bluetoothGatt.getDevice().getAddress();
        characteristicDetail.setmService(service);
        characteristicDetail.setmBluetoothCharact(bluetoothGattCharacteristic);
        characteristicDetail.displayWriteDialog = displayWriteDialog;

        characteristicExpansion.setVisibility(View.VISIBLE);

        // show characteristic's expansion and add the fragment to view/edit characteristic detail
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.add(expansionId, characteristicDetail, CHARACTERISTIC_ADD_FRAGMENT_TRANSACTION_ID);
        fragmentTransaction.commit();

        return characteristicDetail;
    }

    private void setIndicateProperty(BluetoothGattCharacteristic bluetoothGattCharacteristic, String serviceName, ImageView indicatePropertyIcon, TextView indicatePropertyName, ImageView notificationIcon, TextView notificationText) {
        boolean indicationsEnabled = currentWriteReadFragment.getIndicationsEnabled(); // Indication not enabled
        boolean submitted = BLEUtils.SetNotificationForCharacteristic(bluetoothGatt, bluetoothGattCharacteristic, indicationsEnabled ? Notifications.DISABLED : Notifications.INDICATE); // If indication not enabled -> enable
        if (submitted) {
            indicationsEnabled = !indicationsEnabled;
        }

        currentWriteReadFragment.setIndicationsEnabled(indicationsEnabled);
        indicatePropertyIcon.setBackgroundResource(indicationsEnabled ? R.drawable.ic_icon_indicate_on : R.drawable.ic_icon_indicate_off); // enable -> blue, disable -> grey
        indicatePropertyName.setTextColor(ContextCompat.getColor(DeviceServicesActivity.this, indicationsEnabled ? R.color.silabs_blue : R.color.silabs_inactive)); // enable -> blue, disable -> grey

        String characteristicUuid = getUuidFromBluetoothGattCharacteristic(bluetoothGattCharacteristic);
        serviceItemContainers.get(serviceName).setCharacteristicNotificationState(characteristicUuid, indicationsEnabled ? Notifications.INDICATE : Notifications.DISABLED);

        currentWriteReadFragment.setNotificationsEnabled(false);
        if (notificationIcon != null) {
            notificationIcon.setBackgroundResource(R.drawable.ic_icon_notify_off);
            notificationText.setTextColor(ContextCompat.getColor(DeviceServicesActivity.this, R.color.silabs_inactive));
        }
    }

    private void setNotifyProperty(BluetoothGattCharacteristic bluetoothGattCharacteristic, String serviceName, ImageView notifyPropertyIcon, TextView notifyPropertyName, ImageView indicationIcon, TextView indicationText) {
        boolean notificationsEnabled = currentWriteReadFragment.getNotificationsEnabled();
        boolean submitted = BLEUtils.SetNotificationForCharacteristic(bluetoothGatt, bluetoothGattCharacteristic, notificationsEnabled ? Notifications.DISABLED : Notifications.NOTIFY);
        if (submitted) {
            notificationsEnabled = !notificationsEnabled;
        }
        currentWriteReadFragment.setNotificationsEnabled(notificationsEnabled);
        notifyPropertyIcon.setBackgroundResource(notificationsEnabled ? R.drawable.ic_icon_notify_on : R.drawable.ic_icon_notify_off);
        notifyPropertyName.setTextColor(ContextCompat.getColor(DeviceServicesActivity.this, notificationsEnabled ? R.color.silabs_blue : R.color.silabs_inactive));

        String characteristicUuid = getUuidFromBluetoothGattCharacteristic(bluetoothGattCharacteristic);
        serviceItemContainers.get(serviceName).setCharacteristicNotificationState(characteristicUuid, notificationsEnabled ? Notifications.NOTIFY : Notifications.DISABLED);

        currentWriteReadFragment.setIndicationsEnabled(false);
        if (indicationIcon != null) {
            indicationIcon.setBackgroundResource(R.drawable.ic_icon_indicate_off);
            indicationText.setTextColor(ContextCompat.getColor(DeviceServicesActivity.this, R.color.silabs_inactive));
        }
    }

    private TextView getTextViewWithValue(LinearLayout propsContainer, String value) {
        for (int i = 0; i < propsContainer.getChildCount(); i++) {
            if (propsContainer.getChildAt(i).getTag() == null) {
                continue;
            }
            LinearLayout propertyContainer = (LinearLayout) propsContainer.getChildAt(i);
            for (int j = 0; j < propertyContainer.getChildCount(); j++) {
                View view = propertyContainer.getChildAt(j);
                if (view.getTag() != null && view.getTag().equals(PROPERTY_NAME_TAG)) {
                    String propertyValue = ((String) propertyContainer.getTag()).trim().toUpperCase();
                    if (propertyValue.equals(value)) {
                        return (TextView) view;
                    }
                }
            }
        }
        return null;
    }

    private ImageView getIconWithValue(LinearLayout propsContainer, String value) {
        for (int i = 0; i < propsContainer.getChildCount(); i++) {
            if (propsContainer.getChildAt(i).getTag() == null) {
                continue;
            }
            LinearLayout propertyContainer = (LinearLayout) propsContainer.getChildAt(i);
            for (int j = 0; j < propertyContainer.getChildCount(); j++) {
                View view = propertyContainer.getChildAt(j);
                if (view.getTag() != null && view.getTag().equals(PROPERTY_ICON_TAG)) {
                    String propertyValue = ((String) propertyContainer.getTag()).trim().toUpperCase();
                    if (propertyValue.equals(value)) {
                        return (ImageView) view;
                    }
                }
            }
        }
        return null;
    }


    /*
        If characteristic uuid is OTA-specific return its name
        else return unknown_characteristic
     */
    private String getOtaSpecificCharacteristicName(String uuid) {
        uuid = uuid.toUpperCase();
        switch (uuid) {
            case "F7BF3564-FB6D-4E53-88A4-5E37E0326063":
                return "OTA Control Attribute";
            case "984227F3-34FC-4045-A5D0-2C581F81A153":
                return "OTA Data Attribute";
            case "4F4A2368-8CCA-451E-BFFF-CF0E2EE23E9F":
                return "AppLoader version";
            case "4CC07BCF-0868-4B32-9DAD-BA4CC41E5316":
                return "OTA version";
            case "25F05C0A-E917-46E9-B2A5-AA2BE1245AFE":
                return "Gecko Bootloader version";
            case "0D77CC11-4AC1-49F2-BFA9-CD96AC7A92F8":
                return "Application version";
            default:
                return getString(R.string.unknown_characteristic_label);
        }

    }

    private String getUuidFromBluetoothGattCharacteristic(BluetoothGattCharacteristic bluetoothGattCharacteristic) {
        Characteristic characteristic = Engine.getInstance().getCharacteristic(bluetoothGattCharacteristic.getUuid());
        return (characteristic != null ? Common.getUuidText(characteristic.getUuid()) : Common.getUuidText(bluetoothGattCharacteristic.getUuid()));
    }


    /**
     * INITIALIZES ABOUT DIALOG
     *******************************************************/
    private void initAboutDialog() {
        dialogLicense = new Dialog(this);
        dialogLicense.requestWindowFeature(Window.FEATURE_NO_TITLE);
        dialogLicense.setContentView(R.layout.dialog_about_silicon_labs_blue_gecko);
        WebView webView = dialogLicense.findViewById(R.id.menu_item_license);
        Button closeButton = dialogLicense.findViewById(R.id.close_about_btn);
        webView.loadUrl(ABOUT_DIALOG_HTML_ASSET_FILE_PATH);
        closeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dialogLicense.dismiss();
            }
        });
    }

    /**
     * INITIALIZES OTA PROGRESS DIALOG
     *******************************************************/
    private void initOtaProgress() {
        otaProgress = new Dialog(this);
        otaProgress.requestWindowFeature(Window.FEATURE_NO_TITLE);
        otaProgress.setContentView(R.layout.ota_progress);
        TextView address = otaProgress.findViewById(R.id.device_address);
        address.setText(bluetoothGatt.getDevice().getAddress());
        progressBar = otaProgress.findViewById(R.id.otaprogress);
        dataRate = otaProgress.findViewById(R.id.datarate);
        datasize = otaProgress.findViewById(R.id.datasize);
        filename = otaProgress.findViewById(R.id.filename);
        steps = otaProgress.findViewById(R.id.otasteps);
        chrono = otaProgress.findViewById(R.id.chrono);
        OTAStart = otaProgress.findViewById(R.id.otabutton);
        sizename = otaProgress.findViewById(R.id.sizename);
        mtuname = otaProgress.findViewById(R.id.mtuname);
        uploadimage = otaProgress.findViewById(R.id.connecting_spinner);
        OTAStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                otaProgress.dismiss();
                DFUMode("DISCONNECTION");
            }
        });
    }

    /**
     * INITIALIZES OTA SETUP DIALOG
     *******************************************************/
    private void initOtaSetup() {
        otaSetup = new Dialog(this);
        otaSetup.requestWindowFeature(Window.FEATURE_NO_TITLE);
        otaSetup.setContentView(R.layout.ota_config);
        otaSetup.setCancelable(false);
        partialOTA = otaSetup.findViewById(R.id.radio_ota);
        TextView address = otaSetup.findViewById(R.id.device_address);
        address.setText(bluetoothGatt.getDevice().getAddress());
        fullOTA = otaSetup.findViewById(R.id.radio_ota_full);
        final LinearLayout stacklayout = otaSetup.findViewById(R.id.stacklayout);
        OTA_OK = otaSetup.findViewById(R.id.ota_proceed);
        Button OTA_CANCEL = otaSetup.findViewById(R.id.ota_cancel);
        reliableWrite = otaSetup.findViewById(R.id.check_reliable);
        delaySeekBar = otaSetup.findViewById(R.id.delay_seekBar);
        delayText = otaSetup.findViewById(R.id.delay_text);
        delayText.setVisibility(View.INVISIBLE);
        delaySeekBar.setVisibility(View.GONE);
        requestMTU = otaSetup.findViewById(R.id.mtu_seekBar);
        reliabilityRB = otaSetup.findViewById(R.id.reliability_radio_button);
        speedRB = otaSetup.findViewById(R.id.speed_radio_button);

        final EditText mtu_value = otaSetup.findViewById(R.id.mtu_value);

        mtu_value.setOnEditorActionListener(new TextView.OnEditorActionListener() {
            @Override
            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                if (mtu_value.getText() != null) {
                    int test = Integer.parseInt(mtu_value.getText().toString());
                    if (test < 23) test = 23;
                    else if (test > 250) test = 250;
                    requestMTU.setProgress(test - 23);
                    MTU = test;
                }
                return false;

            }
        });


        appLoaderFileButton = otaSetup.findViewById(R.id.select_apploader_file_btn);
        appFileButton = otaSetup.findViewById(R.id.select_app_file_btn);

        appLoaderFileButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent i = new Intent();
                i.setType("*/*");
                i.setAction(Intent.ACTION_GET_CONTENT);
                currentOtaFileType = OtaFileType.APPLOADER;
                startActivityForResult(Intent.createChooser(i, "Choose directory"), FILE_CHOOSER_REQUEST_CODE);
            }
        });

        appFileButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent i = new Intent();
                i.setType("*/*");
                i.setAction(Intent.ACTION_GET_CONTENT);
                currentOtaFileType = OtaFileType.APPLICATION;
                startActivityForResult(Intent.createChooser(i, "Choose directory"), FILE_CHOOSER_REQUEST_CODE);
            }
        });

        requestMTU.setMax(250 - 23);
        requestMTU.setProgress(250 - 23);
        requestMTU.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                mtu_value.setText("" + (progress + 23));
                MTU = progress + 23;
            }
        });

        SeekBar requestPriority = otaSetup.findViewById(R.id.connection_seekBar);
        requestPriority.setMax(2);
        requestPriority.setProgress(2);
        priority = 1;
        requestPriority.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                Log.d("onProgressChanged", "" + progress);
                if (progress == 1) priority = 0;//BALANCE
                else if (progress == 2) priority = 1;//HIGH
                else if (progress == 0) priority = 2;//LOW

            }
        });

        delaySeekBar.setMax(100);
        delaySeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                delayText.setText("" + progress + " ms");
                delayNoResponse = progress;
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });

        OTA_CANCEL.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                otaMode = false;
                otaSetup.dismiss();
                boolOTAbegin = false;
                ota_process = false;
            }
        });
        OTA_OK.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                OTA_OK.setClickable(false);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        otaSetup.dismiss();
                        if (ota_mode) {
                            bluetoothGatt.requestMtu(Integer.parseInt(mtu_value.getText().toString()));
                        } else DFUMode("OTABEGIN");
                    }
                });
            }
        });
        fullOTA.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                stacklayout.setVisibility(View.VISIBLE);
                partialOTA.setBackgroundColor(ContextCompat.getColor(DeviceServicesActivity.this, R.color.silabs_red));
                fullOTA.setBackgroundColor(ContextCompat.getColor(DeviceServicesActivity.this, R.color.silabs_red_selected));
                doubleStepUpload = true;

                if (areFullOTAFilesCorrect()) {
                    OTA_OK.setClickable(true);
                    OTA_OK.setBackgroundColor(ContextCompat.getColor(DeviceServicesActivity.this, R.color.silabs_red));
                } else {
                    OTA_OK.setClickable(false);
                    OTA_OK.setBackgroundColor(ContextCompat.getColor(DeviceServicesActivity.this, R.color.silabs_button_inactive));
                }

            }
        });
        partialOTA.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                stacklayout.setVisibility(View.GONE);
                partialOTA.setBackgroundColor(ContextCompat.getColor(DeviceServicesActivity.this, R.color.silabs_red_selected));
                fullOTA.setBackgroundColor(ContextCompat.getColor(DeviceServicesActivity.this, R.color.silabs_red));
                doubleStepUpload = false;

                if (arePartialOTAFilesCorrect()) {
                    OTA_OK.setClickable(true);
                    OTA_OK.setBackgroundColor(ContextCompat.getColor(DeviceServicesActivity.this, R.color.silabs_red));
                } else {
                    OTA_OK.setClickable(false);
                    OTA_OK.setBackgroundColor(ContextCompat.getColor(DeviceServicesActivity.this, R.color.silabs_button_inactive));
                }

            }
        });

        reliableWrite.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!reliableWrite.isChecked()) {
                    reliable = false;
                    delayText.setVisibility(View.VISIBLE);
                    delaySeekBar.setVisibility(View.VISIBLE);
                } else {
                    delayText.setVisibility(View.INVISIBLE);
                    delaySeekBar.setVisibility(View.GONE);
                    reliable = true;
                }
            }
        });

        reliabilityRB.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                reliable = true;
            }
        });

        speedRB.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                reliable = false;
            }
        });


    }

    /**
     * INITIALIZES MTU DIALOG
     *******************************************************/
    private void initNewMTU() {
        newMTU = new Dialog(this);
        newMTU.requestWindowFeature(Window.FEATURE_NO_TITLE);
        newMTU.setContentView(R.layout.newmtu);
        final TextView mtu_value = newMTU.findViewById(R.id.request_mtu_value);
        SeekBar requestMTU = newMTU.findViewById(R.id.request_mtu_seekBar);
        requestMTU.setMax(250 - 23);
        requestMTU.setProgress(250 - 23);
        requestMTUValue = requestMTU.getProgress() + 23;
        requestMTU.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
            }

            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                mtu_value.setText("" + (progress + 23));
                requestMTUValue = progress + 23;
            }
        });

//        newMTU.setOnShowListener(new DialogInterface.OnShowListener() {
//            @Override
//            public void onShow(DialogInterface dialog) {
//            }
//        });
        Button requestBtn = newMTU.findViewById(R.id.request);
        Button cancelrequest = newMTU.findViewById(R.id.cancel_request);
        requestBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                bluetoothGatt.requestMtu(requestMTUValue);

                newMTU.dismiss();
                if (!boolrequest_mtu) otaSetup.show();
            }
        });
        cancelrequest.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                newMTU.dismiss();
                if (!boolrequest_mtu) otaSetup.show();
            }
        });


    }

    /**
     * INITIALIZES CONNECTION INTERVAL DIALOG
     *******************************************************/
    private void initNewPriority() {
        newPriority = new Dialog(this);
        newPriority.requestWindowFeature(Window.FEATURE_NO_TITLE);
        newPriority.setContentView(R.layout.newpriority);
        Button request = newPriority.findViewById(R.id.request);
        Button cancelrequest = newPriority.findViewById(R.id.cancel_request);
        final CheckBox lowPriority = newPriority.findViewById(R.id.low_priority);
        final CheckBox balancedPriority = newPriority.findViewById(R.id.balanced_priority);
        final CheckBox highPriority = newPriority.findViewById(R.id.high_priority);
        lowPriority.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (lowPriority.isChecked()) {
                    if (highPriority.isChecked()) highPriority.setChecked(false);
                    if (balancedPriority.isChecked()) balancedPriority.setChecked(false);
                }
            }
        });
        balancedPriority.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (balancedPriority.isChecked()) {
                    if (lowPriority.isChecked()) lowPriority.setChecked(false);
                    if (highPriority.isChecked()) highPriority.setChecked(false);
                }
            }
        });
        highPriority.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (highPriority.isChecked()) {
                    if (lowPriority.isChecked()) lowPriority.setChecked(false);
                    if (balancedPriority.isChecked()) balancedPriority.setChecked(false);
                }
            }
        });
        request.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (highPriority.isChecked() || balancedPriority.isChecked() || lowPriority.isChecked()) {
                    if (highPriority.isChecked()) {
                        bluetoothGatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH);
                        Toast.makeText(getApplicationContext(), getResources().getString(R.string.CONNECTION_PRIORITY_HIGH), Toast.LENGTH_SHORT).show();
                    } else if (balancedPriority.isChecked()) {
                        bluetoothGatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_BALANCED);
                        Toast.makeText(getApplicationContext(), getResources().getString(R.string.CONNECTION_PRIORITY_BALANCED), Toast.LENGTH_SHORT).show();
                    } else if (lowPriority.isChecked()) {
                        bluetoothGatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER);
                        Toast.makeText(getApplicationContext(), getResources().getString(R.string.CONNECTION_PRIORITY_LOW), Toast.LENGTH_SHORT).show();
                    }
                }
                newPriority.dismiss();
            }
        });
        cancelrequest.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                newPriority.dismiss();
            }
        });
    }

    /**
     * INITIALIZES LOADING DIALOG
     *******************************************************/
    private void initLoading() {
        loadingdialog = new Dialog(this);
        loadingdialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        loadingdialog.setContentView(R.layout.loadingdialog);
        loadingimage = loadingdialog.findViewById(R.id.connecting_spinner);
        loadingLog = loadingdialog.findViewById(R.id.loadingLog);
        loadingHeader = loadingdialog.findViewById(R.id.loading_header);
    }

    /**
     * SHOWS OTA PROGRESS DIALOG IN UI
     *******************************************************/
    private void showOtaProgress() {
        otaProgress.show();
        OTAStart.setClickable(false);
        otaProgress.setCanceledOnTouchOutside(false);
        DFUMode("OTABEGIN"); //OTAProgress
    }

    /**
     * SHOWS OTA SETUP DIALOG IN UI
     *******************************************************/
    private void showOtaSetup() {
        if (otaSetup != null && !otaSetup.isShowing()) {
            otaSetup.show();
            otaSetup.setCanceledOnTouchOutside(false);

            if (areFullOTAFilesCorrect() && doubleStepUpload) {
                OTA_OK.setClickable(true);
                OTA_OK.setBackgroundColor(ContextCompat.getColor(DeviceServicesActivity.this, R.color.silabs_red));
            } else if (arePartialOTAFilesCorrect() && !doubleStepUpload) {
                OTA_OK.setClickable(true);
                OTA_OK.setBackgroundColor(ContextCompat.getColor(DeviceServicesActivity.this, R.color.silabs_red));
            } else {
                OTA_OK.setClickable(false);
                OTA_OK.setBackgroundColor(ContextCompat.getColor(DeviceServicesActivity.this, R.color.silabs_button_inactive));
            }

            if (reliable) {
                reliableWrite.setChecked(true);
            } else {
                delaySeekBar.setVisibility(View.VISIBLE);
                delayText.setVisibility(View.VISIBLE);
            }

        }
    }

    /**
     * SHOWS OTA SETUP DIALOG IN UI
     *******************************************************/
    private void showLoading() {
        if (loadingdialog != null) {
            loadingdialog.show();
            loadingdialog.setCanceledOnTouchOutside(false);
            animaloading();
        }
    }

    /**
     * SHOWS OTA REQUEST MTU DIALOG IN UI
     *******************************************************/
    private void showRequestMTU() {
        if (newMTU != null) {
            newMTU.show();
            newMTU.setCanceledOnTouchOutside(false);
        }
    }

    /**
     * SHOWS OTA CONNECTION INTERVAL DIALOG IN UI
     *******************************************************/
    private void showRequestPriority() {
        if (newPriority != null) {
            newPriority.show();
            newPriority.setCanceledOnTouchOutside(false);
        }
    }

    /**
     * SHOWS OTA ABOUT DIALOG IN UI
     *******************************************************/
    private void showAboutDialog() {
        dialogLicense.show();
    }

    public int generateNextId() {
        generatedId += 1;
        return generatedId;
    }

    public BluetoothGatt getBluetoothGatt() {
        return bluetoothGatt;
    }

    /**
     * INITILIAZES ALL NECESSARY DIALOGS AND VIEW IN UI - ONCREATE
     ***********************/
    private void onGattFetched() {
        connectionsAdapter.setSelectedDevice(bluetoothGatt.getDevice().getAddress());
        connectionsAdapter.notifyDataSetChanged();
        String deviceName = bluetoothGatt.getDevice().getName();
        deviceName = TextUtils.isEmpty(deviceName) ? getString(R.string.not_advertising_shortcut) : deviceName;
        getSupportActionBar().setTitle(deviceName);
        servicesContainer.removeAllViews();
        initServicesViews();
        initAboutDialog();
        if (!boolOTAbegin) {
            initOtaSetup();
            initOtaProgress();
            initLoading();
            initNewMTU();
            initNewPriority();
        }
        if (btToolbarOpened) {
            closeToolbar();
            btToolbarOpened = !btToolbarOpened;
        }
    }

    /**
     * READ ALL THE SERVICES, PRINT IT ON LOG AND RECOGNIZES HOMEKIT ACCESSORIES
     *****************/
    public void getServicesInfo(BluetoothGatt gatt) {

        List<BluetoothGattService> gattServices = gatt.getServices();
        Log.i("onServicesDiscovered", "Services count: " + gattServices.size());

        for (BluetoothGattService gattService : gattServices) {
            String serviceUUID = gattService.getUuid().toString();
            Log.i("onServicesDiscovered", "Service UUID " + serviceUUID + " - Char count: " + gattService.getCharacteristics().size());
            List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics();

            for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {

                String CharacteristicUUID = gattCharacteristic.getUuid().toString();
                Log.i("onServicesDiscovered", "Characteristic UUID " + CharacteristicUUID + " - Properties: " + gattCharacteristic.getProperties());

                if (gattCharacteristic.getUuid().toString().equals(ota_control.toString())) {
                    if (gattCharacteristics.contains(bluetoothGatt.getService(ota_service).getCharacteristic(ota_data))) {
                        if (!gattServices.contains(bluetoothGatt.getService(homekit_service))) {
                            Log.i("onServicesDiscovered", "Device in DFU Mode");
                        } else {
                            Log.i("onServicesDiscovered", "OTA_Control found");
                            List<BluetoothGattDescriptor> gattDescriptors = gattCharacteristic.getDescriptors();

                            for (BluetoothGattDescriptor gattDescriptor : gattDescriptors) {
                                String descriptor = gattDescriptor.getUuid().toString();

                                if (gattDescriptor.getUuid().toString().equals(homekit_descriptor.toString())) {
                                    kit_descriptor = gattDescriptor;
                                    Log.i("descriptor", "UUID: " + descriptor);
                                    //bluetoothGatt.readDescriptor(gattDescriptor);
                                    byte[] stable = {(byte) 0x00, (byte) 0x00};
                                    homeKitOTAControl(stable);
                                    homekit = true;

                                }
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * WRITES OTA CONTROL FOR HOMEKIT DEVICES
     *****************************************/
    public void homeKitOTAControl(byte[] instanceID) {

        //WRITE CHARACTERISTIC FOR HOMEKIT
        byte[] value = {0x00, 0x02, (byte) 0xee, instanceID[0], instanceID[1], 0x03, 0x00, 0x01, 0x01, 0x01};
        writeGenericCharacteristic(ota_service, ota_control, value);
        Log.d("characteristic", "writting: " + Converters.getHexValue(value));

    }

    /**
     * WRITES BYTE TO OTA CONTROL CHARACTERISTIC
     *****************************************/
    public boolean writeOtaControl(byte ctrl) {
        Log.d("writeOtaControl", "Called");

        if (bluetoothGatt.getService(ota_service) != null) {
            BluetoothGattCharacteristic charac = bluetoothGatt.getService(ota_service).getCharacteristic(ota_control);
            if (charac != null) {
                Log.d("Instance ID", "" + charac.getInstanceId());
                charac.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
                Log.d("charac_properties", "" + charac.getProperties());
                byte[] control = new byte[1];
                control[0] = ctrl;
                charac.setValue(control);
                bluetoothGatt.writeCharacteristic(charac);
                return true;
            } else {
                Log.d("characteristic", "null");
            }
        } else {
            Log.d("service", "null");
        }
        return false;
    }

    /**
     * WRITES BYTE ARRAY TO A GENERIC CHARACTERISTIC
     *****************************************/
    public boolean writeGenericCharacteristic(UUID service, UUID characteristic, byte[] value) {

        if (bluetoothGatt != null) {

            BluetoothGattCharacteristic bluetoothGattCharacteristic = bluetoothGatt.getService(service).getCharacteristic(characteristic);
            Log.d("characteristic", "exists");

            if (bluetoothGattCharacteristic != null) {

                bluetoothGattCharacteristic.setValue(value);
                bluetoothGattCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
                bluetoothGatt.writeCharacteristic(bluetoothGattCharacteristic);
                Log.d("characteristic", "written");

            } else {

                Log.d("characteristic", "null");
                return false;
            }

        } else {

            Log.d("bluetoothGatt", "null");
            return false;

        }

        return true;

    }

    //Not used - White with NO RESPONSE*************************************************/
    public synchronized void whiteOtaData(final byte[] datathread) {
        try {
            boolOTAdata = true;
            byte[] value = new byte[MTU - 3];
            long start = System.nanoTime();
            long current = System.currentTimeMillis();
            int j = 0;
            for (int i = 0; i < datathread.length; i++) {
                value[j] = datathread[i];
                j++;
                if (j >= MTU - 3 || i >= (datathread.length - 1)) {
                    long wait = System.nanoTime();

                    final BluetoothGattCharacteristic charac = bluetoothGatt.getService(ota_service).getCharacteristic(ota_data);
                    charac.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
                    final float progress = ((float) (i + 1) / datathread.length) * 100;
                    final float bitrate = ((float) ((i + 1) * (8.0)) / ((float) ((wait - start) / 1000000.0)));
                    if (j < MTU - 3) {
                        byte[] end = new byte[j];
                        System.arraycopy(value, 0, end, 0, j);
                        Log.d("Progress", "sent " + (i + 1) + " / " + datathread.length + " - " + String.format("%.1f", progress) + " % - " + String.format("%.2fkbit/s", bitrate) + " - " + Converters.getHexValue(end));

                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                datasize.setText((int) progress + " %");
                                progressBar.setProgress((int) progress);
                            }
                        });

                        charac.setValue(end);
                    } else {
                        j = 0;
                        Log.d("Progress", "sent " + (i + 1) + " / " + datathread.length + " - " + String.format("%.1f", progress) + " % - " + String.format("%.2fkbit/s", bitrate) + " - " + Converters.getHexValue(value));

                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                datasize.setText((int) progress + " %");
                                progressBar.setProgress((int) progress);
                            }
                        });

                        charac.setValue(value);
                    }

                    if (bluetoothGatt.writeCharacteristic(charac)) {
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                String datarate = String.format(Locale.US, "%.2fkbit/s", bitrate);
                                dataRate.setText(datarate);
                                //String dataSize = String.format("%.2fkbit/s", (float) datathread.length/1000);
                            }
                        });

                        while ((System.nanoTime() - wait) / 1000000.0 < delayNoResponse) ;
                    } else {
                        do {
                            while ((System.nanoTime() - wait) / 1000000.0 < delayNoResponse) ;
                            wait = System.nanoTime();

                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    String datarate = String.format(Locale.US, "%.2fkbit/s", bitrate);
                                    dataRate.setText(datarate);
                                    //String dataSize = String.format("%.2fkbit/s", (float) datathread.length/1000);
                                }
                            });

                        } while (!bluetoothGatt.writeCharacteristic(charac));
                    }
                }
            }
            long end = System.currentTimeMillis();
            float time = (end - start) / 1000L;
            Log.d("OTA Time - ", "" + time + "s");
            boolOTAdata = false;
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    chrono.stop();
                    uploadimage.clearAnimation();
                    uploadimage.setVisibility(View.INVISIBLE);
                }
            });
            DFUMode("OTAEND");
        } catch (NullPointerException e) {
            e.printStackTrace();
        }
    }

    /**
     * WRITES EBL/GBL FILES TO OTA_DATA CHARACTERISTIC
     *****************************************/
    public synchronized void otaWriteDataReliable() {

        boolOTAdata = true;
        if (pack == 0) {
            /**SET MTU_divisible by 4*/
            int minus = 0;
            do {
                MTU_divisible = MTU - 3 - minus;
                minus++;
            } while (!(MTU_divisible % 4 == 0));

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    mtuname.setText(MTU_divisible + " bytes");
                }
            });
        }

        byte[] writearray;
        final float pgss;

        if (pack + MTU_divisible > otafile.length - 1) {
            /**SET last by 4*/
            int plus = 0;
            int last = otafile.length - pack;
            do {
                last = last + plus;
                plus++;
            } while (!(last % 4 == 0));
            writearray = new byte[last];
            int j = 0;
            for (int i = pack; i < pack + last; i++) {
                if (otafile.length - 1 < i) {
                    writearray[j] = (byte) 0xFF;
                } else writearray[j] = otafile[i];
                j++;

            }
            pgss = ((float) (pack + last) / (otafile.length - 1)) * 100;
            Log.d("characte", "last: " + pack + " / " + (pack + last) + " : " + Converters.getHexValue(writearray));
        } else {
            int j = 0;
            writearray = new byte[MTU_divisible];
            for (int i = pack; i < pack + MTU_divisible; i++) {
                writearray[j] = otafile[i];
                j++;
            }
            pgss = ((float) (pack + MTU_divisible) / (otafile.length - 1)) * 100;
            Log.d("characte", "pack: " + pack + " / " + (pack + MTU_divisible) + " : " + Converters.getHexValue(writearray));
        }

        BluetoothGattCharacteristic charac = bluetoothGatt.getService(ota_service).getCharacteristic(ota_data);
        charac.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
        charac.setValue(writearray);
        bluetoothGatt.writeCharacteristic(charac);

        final long waiting_time = (System.currentTimeMillis() - otatime);
        final float bitrate = 8 * (float) pack / waiting_time;

        if (pack > 0) {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            progressBar.setProgress((int) pgss);
                            String datarate = String.format(Locale.US, "%.2fkbit/s", bitrate);
                            dataRate.setText(datarate);
                            datasize.setText((int) pgss + " %");

                        }
                    });
                }
            });
        } else {
            otatime = System.currentTimeMillis();
        }
    }

    /**
     * (RUNNABLE) CHECKS OTA BEGIN BOX AND STARTS
     **********************************/
    private Runnable checkbeginrunnable = new Runnable() {
        @Override
        public void run() {
            chrono.setBase(SystemClock.elapsedRealtime());
            chrono.start();
        }
    };

    /**
     * CREATES BAR PROGRESS ANIMATION IN LOADING AND OTA PROGRESS DIALOG
     **********************************/
    private void animaloading() {
        if (uploadimage != null && loadingimage != null && otaProgress != null) {
            uploadimage.setVisibility(View.GONE);
            loadingimage.setVisibility(View.GONE);
            if (loadingdialog.isShowing()) {
                loadingimage.setVisibility(View.VISIBLE);
            }
            if (otaProgress.isShowing()) {
                uploadimage.setVisibility(View.VISIBLE);
            }
        }
    }

    /**
     * OTA STATE MACHINE
     */
    public synchronized void DFUMode(String step) {

        switch (step) {

            case "INIT":
                DFUMode("OTABEGIN");
                break;

            /**WRITES 0x00 TO OTA_CONTROL CHARACTERISTIC*/
            case "OTABEGIN":
                if (ota_mode) {
                    //START OTA PROCESS -> gattCallback -> OnCharacteristicWrite
                    Log.d("OTA_BEGIN","true");
                    handler.postDelayed(WRITE_OTA_CONTROL_ZERO, 200);
                } else {
                    //PUT DEVICE IN DFUMODE -> gattCallback -> OnCharacteristicWrite
                    if (homekit) {
                        bluetoothGatt.readDescriptor(kit_descriptor);
                    } else {
                        Log.d("DFU_MODE","true");
                        handler.postDelayed(WRITE_OTA_CONTROL_ZERO, 200);
                    }
                }
                break;

            /**SET THE FILES TO BE UPLOADED TO OTA_DATA CHARACTERISTIC*/
            case "OTAUPLOAD":
                Log.d("OTAUPLOAD", "Called");
                /**Check Services*/

                BluetoothGattService mBluetoothGattService = bluetoothGatt.getService(ota_service);
                if (mBluetoothGattService != null) {
                    BluetoothGattCharacteristic charac = bluetoothGatt.getService(ota_service).getCharacteristic(ota_data);
                    if (charac != null) {
                        charac.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
                        Log.d("Instance ID", "" + charac.getInstanceId());

                        /**Check Files*/

                        byte[] ebl = null;
                        try {
                            Log.d("stackPath", "" + stackPath);
                            Log.d("appPath", "" + appPath);
                            File file;

                            if (!stackPath.equals("") && doubleStepUpload) {
                                file = new File(stackPath);
                                boolFullOTA = true;
                            } else {
                                file = new File(appPath);
                                boolFullOTA = false;
                            }
                            FileInputStream fileInputStream = new FileInputStream(file);
                            int size = fileInputStream.available();
                            Log.d("size", "" + size);
                            byte[] temp = new byte[size];
                            fileInputStream.read(temp);
                            fileInputStream.close();
                            ebl = temp;
                        } catch (Exception e) {
                            Log.e("InputStream", "Couldn't open file" + e);
                        }
                        final byte[] datathread = ebl;
                        otafile = ebl;

                        /**Check if it is partial of full OTA*/

                        final String fn;
                        if (!stackPath.equals("") && doubleStepUpload) {
                            int last = stackPath.lastIndexOf(File.separator);
                            fn = stackPath.substring(last);
                            Log.d("CurrentlyUpdating", "apploader");
                        } else {
                            int last = appPath.lastIndexOf(File.separator);
                            fn = appPath.substring(last);
                            Log.d("CurrentlyUpdating", "appliaction");
                        }
                        pack = 0;

                        /**Prepare information about current upload step*/

                        final String stepInfo;
                        if (doubleStepUpload) {
                            if (!stackPath.equals("")) {
                                stepInfo = "1 OF 2";
                            } else {
                                stepInfo = "2 OF 2";
                            }
                        } else {
                            stepInfo = "1 OF 1";
                        }

                        /**Set info into UI OTA Progress*/
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                filename.setText(fn);
                                steps.setText(stepInfo);
                                sizename.setText(datathread.length + " bytes");
                                mtuname.setText(Integer.toString(MTU));
                                uploadimage.setVisibility(View.VISIBLE);
                                animaloading();
                            }
                        });

                        /**Start OTA_data Upload in another thread*/
                        Thread otaUpload = new Thread(new Runnable() {
                            @Override
                            public void run() {
                                if (reliable) {
                                    otaWriteDataReliable();
                                } else whiteOtaData(datathread);
                            }
                        });
                        otaUpload.start();
                    }
                }
                break;

            /**WRITES 0x03 TO OTA_CONTROL CHARACTERISTIC*/
            case "OTAEND":
                Log.d("OTAEND", "Called");
                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        writeOtaControl((byte) 0x03);
                    }
                }, 500);
                break;

            /**ENDS THE OTA PROCESS*/
            case "DISCONNECTION":
                ota_process = false;
                boolFullOTA = false;
                boolOTAbegin = false;
                disconnectGatt(bluetoothGatt);
                break;
            default:
                break;
        }

    }

    //Not used - Using reconnect() instead
    private void resetconnection() {
        Timer scanTimer = new Timer();

        if (bluetoothGatt != null) {

            reconnectaddress = bluetoothGatt.getDevice().getAddress();

            bluetoothDevice = bluetoothGatt.getDevice();

            bluetoothGatt.disconnect();
            service.clearCache();
            refreshDeviceCache(bluetoothGatt);


            scanTimer.schedule(new TimerTask() {
                @Override
                public void run() {
                    bluetoothBinding.unbind();
                    service.clearGatt();
                    bluetoothGatt.close();
                }
            }, 400);

            scanTimer.schedule(new TimerTask() {
                @Override
                public void run() {
                    Log.d("fetchUUIDs", "" + bluetoothDevice.fetchUuidsWithSdp());
                }
            }, 500);

            scanTimer.schedule(new TimerTask() {
                @Override
                public void run() {
                    bluetoothGatt = null;
                    bluetoothDevice = null;
                }
            }, 600);

        }

        scanTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
                bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
                bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
            }
        }, 1500);

        scanTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        startScanLeDevice();
                        loadingLog.setText("Scanning...");
                    }
                });
            }
        }, 3000);

    }

    //Not used - Reconnect with device after scanner
    private void reconnectGatt(final BluetoothDevice btDevice) {
        bluetoothDevice = btDevice;
        stopScan();

        Timer reconnectTimer = new Timer();
        reconnectTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                bluetoothBinding = new BlueToothService.Binding(getApplicationContext()) {
                    @Override
                    protected void onBound(final BlueToothService service) {
                        service.connectGatt(bluetoothDevice, false, gattCallback);
                        bluetoothGatt = service.getConnectedGatt();
                    }
                };
                BlueToothService.bind(bluetoothBinding);
            }
        }, delayToConnect);
    }

    //Not Used - resetconnection
    private ScanCallback reScanCallback;

    //Not Used - resetconnection
    BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() {
        @Override
        public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord) {
            Log.d("Scanning", "");
            if (device.getAddress().equals(reconnectaddress)) {
                bluetoothGatt = device.connectGatt(getBaseContext(), false, gattCallback);
                Log.d("onLeScan", "Device is found" + device.getAddress());
            }
        }
    };

    //Not Used - resetconnection
    private void startScanLeDevice() {
        ScanFilter macaddress = new ScanFilter.Builder().setDeviceAddress(reconnectaddress).build();
        ArrayList<ScanFilter> filters = new ArrayList<>();
        filters.add(macaddress);

        ScanSettings settings = new ScanSettings.Builder().build();
        bluetoothLeScanner.startScan(filters, settings, reScanCallback);

        Log.d("startScanLeDevice", "Scan Started");
    }

    //Not Used - resetconnection
    private void stopScan() {
        bluetoothLeScanner.stopScan(reScanCallback);
        Log.d("stopScan", "Called");
    }


    /**
     * CALLS A METHOD TO CLEAN DEVICE SERVICES
     ********************************************************/
    private boolean refreshDeviceCache(final BluetoothGatt gatt) {
        try {
            Log.d("refreshDevice", "Called");
            Method localMethod = bluetoothGatt.getClass().getMethod("refresh");
            if (localMethod != null) {
                boolean bool = ((Boolean) localMethod.invoke(bluetoothGatt, new Object[0])).booleanValue();
                Log.d("refreshDevice", "bool: " + bool);
                return bool;
            }
        } catch (Exception localException) {
            Log.e("refreshDevice", "An exception occured while refreshing device");
        }
        return false;
    }

    /**
     * DISCONNECT GATT GENTLY AND CLEAN GLOBAL VARIABLES
     ***************************************************/
    public void disconnectGatt(BluetoothGatt gatt) {
        Timer disconnectTimer = new Timer();
        boolFullOTA = false;
        boolOTAbegin = false;
        running = false;
        ota_process = false;
        disconnect_gatt = true;
        UICreated = false;
        if (gatt != null && gatt.getDevice() != null) {

            if (loadingdialog == null) {
                initLoading();
            }

            final BluetoothGatt btGatt = gatt;
            disconnectTimer.schedule(new TimerTask() {
                @Override
                public void run() {
                    /**Getting bluetoothDevice to FetchUUID*/
                    if (btGatt.getDevice() != null) bluetoothDevice = btGatt.getDevice();
                    /**Disconnect gatt*/
                    btGatt.disconnect();
                    service.clearGatt();
                    Log.d("disconnectGatt", "gatt disconnect");
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            showLoading();
                            loadingLog.setText("Disconnecting...");
                            loadingHeader.setText("GATT Connection");
                        }
                    });
                }
            }, 200);

            disconnectTimer.schedule(new TimerTask() {
                @Override
                public void run() {
                    bluetoothDevice.fetchUuidsWithSdp();
                }
            }, 300);


            disconnectionTimeout = true;

            Runnable timeout = new Runnable() {
                @Override
                public void run() {
                    handler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            if (disconnectionTimeout) {
                                finish();
                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        Toast.makeText(getBaseContext(), "DISCONNECTION PROBLEM", Toast.LENGTH_LONG).show();
                                    }
                                });
                            }
                        }
                    }, 5000);
                }
            };
            new Thread(timeout).start();


        } else {
            finish();
        }
    }

    /**
     * CLEANS USER INTERFACE AND FINISH ACTIVITY
     *********************************************************/
    public void exit(BluetoothGatt gatt) {
        gatt.close();
        if (service.getConnectedGatt() != null) {
            service.getConnectedGatt().close();
        }
        service.clearCache();
        bluetoothBinding.unbind();
        disconnect_gatt = false;
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                bluetoothGatt = null;
                service = null;
                bluetoothBinding = null;
                if (loadingdialog != null && loadingdialog.isShowing()) loadingdialog.dismiss();
                if (otaProgress != null && otaProgress.isShowing()) otaProgress.dismiss();
                if (otaSetup != null && otaSetup.isShowing()) otaSetup.dismiss();
                finish();
            }
        }, 1000);
    }

    /**
     * DISCONNECTS AND CONNECTS WITH THE SELECTED DELAY
     *******************************************************/
    public void reconnect(long delaytoconnect) {

        Timer reconnectTimer = new Timer();
        bluetoothDevice = bluetoothGatt.getDevice();

        if (service.isGattConnected()) {
            service.clearGatt();
            service.clearCache();
        }

        bluetoothGatt.disconnect();

        reconnectTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                bluetoothGatt.close();
                bluetoothBinding.unbind();
            }
        }, 400);


        reconnectTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        if (loadingdialog.isShowing()) {
                            loadingLog.setText("Attempting connection...");
                        }
                    }
                });
                bluetoothBinding = new BlueToothService.Binding(getApplicationContext()) {
                    @Override
                    protected void onBound(final BlueToothService service) {
                        bluetoothGatt = bluetoothDevice.connectGatt(getApplicationContext(), false, gattCallback);
                    }
                };
                BlueToothService.bind(bluetoothBinding);
            }
        }, delaytoconnect);
    }


    /**
     * ANIMATIONS CONTROLLERS
     ******************************************************************************/
    public void showCharacteristicLoadingAnimation() {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                loadingContainer.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        // this onclicklistener prevents services and characteristics from user interaction before ui is loaded
                    }
                });
                Animation loadingGradientAnimation = AnimationUtils.loadAnimation(DeviceServicesActivity.this, R.anim.connection_translate_right);
                loadingContainer.setVisibility(View.VISIBLE);
                loadingGradientContainer.startAnimation(loadingGradientAnimation);
                Animation loadingBarFlyIn = AnimationUtils.loadAnimation(DeviceServicesActivity.this, R.anim.scanning_bar_fly_in);
                loadingBarContainer.startAnimation(loadingBarFlyIn);
            }
        });
    }

    public void hideCharacteristicLoadingAnimation() {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                loadingGradientContainer.clearAnimation();
                Animation loadingBarFlyIn = AnimationUtils.loadAnimation(DeviceServicesActivity.this, R.anim.scanning_bar_fly_out);
                loadingBarContainer.startAnimation(loadingBarFlyIn);

                loadingBarFlyIn.setAnimationListener(new Animation.AnimationListener() {
                    @Override
                    public void onAnimationStart(Animation animation) {

                    }

                    @Override
                    public void onAnimationEnd(Animation animation) {
                        loadingContainer.setVisibility(View.GONE);
                    }

                    @Override
                    public void onAnimationRepeat(Animation animation) {

                    }
                });
            }
        });
    }

    /********************************************************************************************************/

    /**
     * ATTENTION: This was auto-generated to implement the App Indexing API.
     * See https://g.co/AppIndexing/AndroidStudio for more information.
     */
    public Action getIndexApiAction() {
        Thing object = new Thing.Builder()
                .setName("DeviceServices Page") // TODO: Define a title for the content shown.
                // TODO: Make sure this auto-generated URL is correct.
                .setUrl(Uri.parse("http://[ENTER-YOUR-URL-HERE]"))
                .build();
        return new Action.Builder(Action.TYPE_VIEW)
                .setObject(object)
                .setActionStatus(Action.STATUS_TYPE_COMPLETED)
                .build();
    }

    @Override
    public void onStart() {
        super.onStart();

        // ATTENTION: This was auto-generated to implement the App Indexing API.
        // See https://g.co/AppIndexing/AndroidStudio for more information.
        client.connect();
        AppIndex.AppIndexApi.start(client, getIndexApiAction());
    }

    @Override
    public void onStop() {
        super.onStop();

        // ATTENTION: This was auto-generated to implement the App Indexing API.
        // See https://g.co/AppIndexing/AndroidStudio for more information.
        AppIndex.AppIndexApi.end(client, getIndexApiAction());
        client.disconnect();
    }

    private void animateToolbarOpen(int openPercentHeight, int duration) {
        ValueAnimator animator = ValueAnimator.ofInt(0, percentHeightToPx(openPercentHeight)).setDuration(duration);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Integer value = (Integer) animation.getAnimatedValue();
                frameLayout.getLayoutParams().height = value;
                frameLayout.requestLayout();
            }
        });

        frameLayout.setVisibility(View.VISIBLE);
        ViewCompat.setTranslationZ(frameLayoutContainerRL, 5f);
        AnimatorSet set = new AnimatorSet();
        set.play(animator);
        set.setInterpolator(new AccelerateDecelerateInterpolator());
        set.start();
    }

    private void closeToolbar() {
        animateToolbarClose(TOOLBAR_CLOSE_PERCENTACE, 300);
        setToolbarItemsNotClicked();
        bluetoothBrowserBackgroundRL.setVisibility(View.GONE);
    }

    private void setToolbarItemClicked(ImageView imageView, TextView textView) {
        textView.setTextColor(ContextCompat.getColor(this, R.color.silabs_blue));
        DrawableCompat.setTint(imageView.getDrawable(), ContextCompat.getColor(this, R.color.silabs_blue));
    }

    private void setToolbarItemsNotClicked() {
        connectionsTV.setTextColor(ContextCompat.getColor(this, R.color.silabs_primary_text));
        DrawableCompat.setTint(connectionsIV.getDrawable(), ContextCompat.getColor(this, R.color.silabs_primary_text));

        logTV.setTextColor(ContextCompat.getColor(this, R.color.silabs_primary_text));
        DrawableCompat.setTint(logIV.getDrawable(), ContextCompat.getColor(this, R.color.silabs_primary_text));
    }

    private void animateToolbarClose(int openPercentHeight, int duration) {
        ValueAnimator animator = ValueAnimator.ofInt(percentHeightToPx(openPercentHeight), 0).setDuration(duration);


        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Integer value = (Integer) animation.getAnimatedValue();
                frameLayout.getLayoutParams().height = value;
                frameLayout.requestLayout();
            }
        });

        AnimatorSet set = new AnimatorSet();
        set.play(animator);
        set.setInterpolator(new AccelerateDecelerateInterpolator());
        set.start();
    }

    private int percentHeightToPx(int percent) {
        if (percent < 0 || percent > 100) throw new IllegalArgumentException();
        int height = servicesWrapper.getHeight();
        return (int) (((float) percent / 100.0) * height);
    }

    private void setToolbarFragment(Fragment fragment) {
        androidx.fragment.app.FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
        fragmentTransaction.replace(R.id.frame_layout, fragment);
        fragmentTransaction.commit();
    }

    private void fragmentsInit() {
        loggerFragment = new LoggerFragment().setCallback(new ToolbarCallback() {
            @Override
            public void close() {
                closeToolbar();
                btToolbarOpened = !btToolbarOpened;
            }

            @Override
            public void submit(FilterDeviceParams filterDeviceParams, boolean close) {

            }
        });
        loggerFragment.setAdapter(new LogAdapter(Constants.LOGS, getApplicationContext()));


        connectionsFragment = new ConnectionsFragment().setCallback(new ToolbarCallback() {
            @Override
            public void close() {
                closeToolbar();
                btToolbarOpened = !btToolbarOpened;
            }

            @Override
            public void submit(FilterDeviceParams filterDeviceParams, boolean close) {

            }
        });
        connectionsAdapter = new ConnectionsAdapter(getConnectedBluetoothDevices(), getApplicationContext());
        connectionsFragment.setAdapter(connectionsAdapter);
        connectionsFragment.getAdapter().setServicesConnectionsCallback(this);
        connectionsTV.setText(getConnectedBluetoothDevices().size() + " Connections");
    }

    private List<BluetoothDevice> getConnectedBluetoothDevices() {
        BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        return bluetoothManager.getConnectedDevices(BluetoothProfile.GATT);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if (requestCode == WRITE_EXTERNAL_STORAGE_REQUEST_PERMISSION) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(DeviceServicesActivity.this, getResources().getString(R.string.Permissions_granted_succesfully), Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(DeviceServicesActivity.this, R.string.permissions_not_granted, Toast.LENGTH_LONG).show();
            }
        }
    }

    @Override
    public void onDisconnectClicked(final BluetoothDeviceInfo deviceInfo) {
        final String currenctDeviceAddr = bluetoothGatt.getDevice().getAddress();

        bluetoothBinding = new BlueToothService.Binding(DeviceServicesActivity.this) {
            @Override
            protected void onBound(BlueToothService service) {
                boolean success = service.disconnectGatt(deviceInfo.getAddress());
                if (!success) {
                    Toast.makeText(DeviceServicesActivity.this, R.string.device_not_from_EFR, Toast.LENGTH_LONG).show();
                }
                updateCountOfConnectedDevices();
                connectionsFragment.getAdapter().notifyDataSetChanged();

                if (currenctDeviceAddr.equals(deviceInfo.getAddress())) {

                    if (getConnectedBluetoothDevices().size() <= 0) finish();
                    else {
                        BluetoothDevice device = getConnectedBluetoothDevices().get(0);
                        changeDevice(device.getAddress());
                    }
                }
            }
        };
        BlueToothService.bind(bluetoothBinding);

    }

    @Override
    public void onDeviceClicked(final BluetoothDeviceInfo device) {
        boolOTAbegin = false;
        changeDevice(device.getAddress());
    }

    private void initDevice(final String deviceAddress) {
        handler = new Handler();
        bluetoothBinding = new BlueToothService.Binding(this) {
            @Override
            protected void onBound(BlueToothService service) {//todo dubel
                serviceHasBeenSet = true;
                DeviceServicesActivity.this.service = service;
                if (!service.isGattConnected(deviceAddress)) {
                    Toast.makeText(DeviceServicesActivity.this, R.string.toast_debug_connection_failed, Toast.LENGTH_LONG).show();
                    disconnectGatt(bluetoothGatt);
                } else {
                    BluetoothGatt bG = service.getConnectedGatt(deviceAddress);
                    if (bG == null) {
                        Toast.makeText(DeviceServicesActivity.this, R.string.device_not_from_EFR, Toast.LENGTH_LONG).show();
                        finish();
                        return;
                    }
                    service.registerGattCallback(true, gattCallback);
                    if (bG.getServices() != null && !bG.getServices().isEmpty()) {
                        bluetoothGatt = bG;
                        onGattFetched();
                    } else {
                        showCharacteristicLoadingAnimation();
                        bG.discoverServices();
                    }
                }
            }
        };

        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        BlueToothService.bind(bluetoothBinding);
                    }
                });
            }
        }, UI_CREATION_DELAY);
    }


    public void changeDevice(final String address) {
        servicesContainer.removeAllViews();
        loggerFragment.getAdapter().logByDeviceAddress(address);

        initDevice(address);
    }


    public void updateCountOfConnectedDevices() {
        final List<BluetoothDevice> connectedBluetoothDevices = getConnectedBluetoothDevices();
        final int size = connectedBluetoothDevices.size();
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                connectionsTV.setText(getResources().getString(R.string.n_Connections, size));
                connectionsFragment.getAdapter().setConnectionsList(connectedBluetoothDevices);
                connectionsFragment.getAdapter().notifyDataSetChanged();
            }
        });
    }

    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);


        if (resultCode == RESULT_OK) {
            switch (requestCode) {
                case FILE_CHOOSER_REQUEST_CODE:
                    OtaFileType type = currentOtaFileType;
                    Uri uri = data.getData();
                    String filename;

                    try {
                        filename = getFileName(uri);
                    } catch (Exception e) {
                        filename = "";
                    }

                    if (!hasOtaFileCorrectExtension(filename)) {
                        Toast.makeText(DeviceServicesActivity.this, getResources().getString(R.string.Incorrect_file), Toast.LENGTH_SHORT).show();
                        return;
                    }

                    // APPLICATION
                    if (type.equals(OtaFileType.APPLICATION)) {
                        prepareOtaFile(uri, OtaFileType.APPLICATION, filename);
                        // APPLOADER
                    } else {
                        prepareOtaFile(uri, OtaFileType.APPLOADER, filename);
                    }
                    break;
            }
        }

        if (areFullOTAFilesCorrect() && doubleStepUpload) {
            OTA_OK.setClickable(true);
            OTA_OK.setBackgroundColor(ContextCompat.getColor(DeviceServicesActivity.this, R.color.silabs_red));
        } else if (arePartialOTAFilesCorrect() && !doubleStepUpload) {
            OTA_OK.setClickable(true);
            OTA_OK.setBackgroundColor(ContextCompat.getColor(DeviceServicesActivity.this, R.color.silabs_red));
        } else {
            OTA_OK.setClickable(false);
            OTA_OK.setBackgroundColor(ContextCompat.getColor(DeviceServicesActivity.this, R.color.silabs_button_inactive));
        }

    }


    private boolean areFullOTAFilesCorrect() {
        return !appFileButton.getText().equals(getString(R.string.Select_Application_gbl_file)) && !appLoaderFileButton.getText().equals(getString(R.string.Select_Apploader_gbl_file));
    }

    private boolean arePartialOTAFilesCorrect() {
        return !appFileButton.getText().equals(getString(R.string.Select_Application_gbl_file));
    }

    /*
    public String getFileName(Uri uri) {
        Cursor cursor = getContentResolver().query(uri, null, null, null, null);
        if (cursor == null) return ""; //If cursor is null return empty string

        int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
        cursor.moveToFirst();
        String name = cursor.getString(nameIndex);
        cursor.close();

        return name;
    }
*/
    public String getFileName(Uri uri) {
        String result = null;
        if (uri.getScheme().equals("content")) {
            Cursor cursor = getContentResolver().query(uri, null, null, null, null);
            try {
                if (cursor != null && cursor.moveToFirst()) {
                    result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
                }
            } finally {
                cursor.close();
            }
        }
        if (result == null) {
            result = uri.getPath();
            int cut = result.lastIndexOf('/');
            if (cut != -1) {
                result = result.substring(cut + 1);
            }
        }
        return result;
    }

    public boolean hasOtaFileCorrectExtension(String filename) {
        if (filename.toUpperCase().contains(".GBL")) return true;
        return false;
    }

    public void prepareOtaFile(Uri uri, OtaFileType type, String filename) {

        try {
            InputStream is = getContentResolver().openInputStream(uri);

            if (is == null) {
                Toast.makeText(DeviceServicesActivity.this, getResources().getString(R.string.There_was_a_problem_while_preparing_the_file), Toast.LENGTH_SHORT).show();
                return;
            }

            File file = new File(getCacheDir(), filename);

            OutputStream output = new FileOutputStream(file);
            byte[] buffer = new byte[4 * 1024];
            int read;

            while ((read = is.read(buffer)) != -1) {
                output.write(buffer, 0, read);
            }

            if (type.equals(OtaFileType.APPLICATION)) {
                appPath = file.getAbsolutePath();
                appFileButton.setText(filename);
            } else {
                stackPath = file.getAbsolutePath();
                appLoaderFileButton.setText(filename);
            }

            output.flush();
        } catch (IOException e) {
            e.printStackTrace();
            Toast.makeText(DeviceServicesActivity.this, getResources().getString(R.string.Incorrect_file), Toast.LENGTH_SHORT).show();
        }

    }

}