public class MainActivity extends AppCompatActivity {
    public static final int CALLBACK_FILEPICKER_UAVO = 3456;
    public static final int CALLBACK_TTS = 6574;
    public static final int POLL_WAIT_TIME = 500;
    public static final int POLL_SECOND_FACTOR = 1000 / POLL_WAIT_TIME;
    public static final int SERIAL_BLUETOOTH = 2;
    public static final int SERIAL_NONE = 0;
    public static final int SERIAL_LOG_FILE = 3;
    protected static final int SERIAL_USB = 1;
    private static final int NUM_OF_VIEWS = 12; //TODO: NEEDED?
    private static final String ASSETS_LOGS_INTERNAL_PATH = "logs";
    private static final String ASSETS_UAVO_INTERNAL_PATH = "uavo";
    static int mCurrentView = 0;
    private static boolean mHasPThread = false;
    public ImageView imgSerial;
    public ImageView imgToolbarFlightSettings;
    public ImageView imgToolbarLocalSettings;
    public ImageView imgUavoSanity;
    public BluetoothAdapter mBluetoothAdapter;
    public boolean mDoReconnect = false;
    public FcDevice mFcDevice;
    public PollThread mPollThread = null;
    public Map<Integer, ViewController> mVcList;
    public SparseArray<View> mViews;
    public Map<String, UAVTalkXMLObject> mXmlObjects = null;
    public MenuItem menDebug = null;
    public ViewController mCurrentParentViewController;
    protected ImageView imgFlightTelemetry;
    protected ImageView imgGroundTelemetry;
    private ConnectionThread mConnectionThread = null;
    private android.hardware.usb.UsbDevice mDevice;
    private UsbDeviceConnection mDeviceConnection;
    private DrawerLayout mDrawerLayout;
    private ListView mDrawerList;
    private ListView mDrawerListRight;
    private CharSequence mDrawerTitle;
    private ActionBarDrawerToggle mDrawerToggle;
    private UsbInterface mInterface;
    private PendingIntent mPermissionIntent = null;
    private long mRxObjectsBad;
    private long mRxObjectsGood;
    private CharSequence mTitle;
    private TextToSpeechHelper mTtsHelper;
    private long mTxObjects;
    private String mUavoLongHash;
    private String mUavoLongHashFc;
    private UsbManager mUsbManager = null;
    private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            VisualLog.d(action);

            if (SettingsHelper.mSerialModeUsed == SERIAL_USB &&
                    UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
                android.hardware.usb.UsbDevice device =
                        intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

                VisualLog.d(device.getVendorId() +
                        getString(R.string.DASH) + device.getProductId() +
                        getString(R.string.DASH) + device.getDeviceClass() +
                        getString(R.string.SPACE) + device.getDeviceSubclass() +
                        getString(R.string.SPACE) + device.getDeviceProtocol());

                if (device.getDeviceClass() == UsbConstants.USB_CLASS_MISC) {
                    try {
                        mUsbManager.requestPermission(device, mPermissionIntent);
                    } catch (SecurityException e) {
                        VisualLog.e("MAINACTIVITY", e.getMessage(), e);
                    }
                }

            } else if (SettingsHelper.mSerialModeUsed == SERIAL_USB
                    && UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
                android.hardware.usb.UsbDevice device =
                        intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                if (device != null) {
                    setUsbInterface(null, null);
                    if (mFcDevice != null) {
                        mFcDevice.stop();
                    }
                }
            } else if (SettingsHelper.mSerialModeUsed == SERIAL_USB &&
                    getString(R.string.ACTION_USB_PERMISSION).equals(action)) {
                synchronized (this) {
                    android.hardware.usb.UsbDevice device =
                            intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                    if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                        if (device != null) {
                            UsbInterface intf = findAdbInterface(device);
                            if (intf != null) {
                                setUsbInterface(device, intf);
                            }
                        }
                    } else {
                        VisualLog.d("permission denied for device " + mDevice);
                    }
                }
            }
        }
    };
    private ArrayList<Integer> mViewPos;
    private ArrayList<Integer> mViewPosRight;
    private FirebaseAnalytics mFirebaseAnalytics;

    public static boolean hasPThread() {
        return mHasPThread;
    }

    static private UsbInterface findAdbInterface(android.hardware.usb.UsbDevice device) {

        int count = device.getInterfaceCount();
        for (int i = 0; i < count; i++) {
            UsbInterface intf = device.getInterface(i);
            if (intf.getInterfaceClass() == 3
                    && intf.getInterfaceSubclass() == 0
                    && intf.getInterfaceProtocol() == 0) {
                return intf;
            }
        }
        return null;
    }

    public static void setPThread(boolean mHasPThread) {
        MainActivity.mHasPThread = mHasPThread;
    }

    public FcDevice getFcDevice() {
        return mFcDevice;
    }

    public long getRxObjectsBad() {
        return mRxObjectsBad;
    }

    public synchronized void setRxObjectsBad(long o) {
        mRxObjectsBad = o;
    }

    public long getRxObjectsGood() {
        return mRxObjectsGood;
    }

    public synchronized void setRxObjectsGood(long o) {
        mRxObjectsGood = o;
    }

    public TextToSpeechHelper getTtsHelper() {
        return mTtsHelper;
    }

    public long getTxObjects() {
        return mTxObjects;
    }

    public synchronized void setTxObjects(long o) {
        mTxObjects = o;
    }

    public String getUavoLongHash() {
        return mUavoLongHash;
    }

    public ConnectionThread getConnectionThread() {
        return mConnectionThread;
    }

    public void setPollThreadObjectTree(UAVTalkObjectTree oTree) {
        if (mPollThread != null) {
            mPollThread.setObjectTree(oTree);
        }
    }

    public void setUavoLongHashFC(String uavolonghashfc) {
        mUavoLongHashFc = uavolonghashfc;
    }

    public synchronized void incRxObjectsGood() {
        mRxObjectsGood++;
    }

    public synchronized void incRxObjectsBad() {
        mRxObjectsBad++;
    }

    public synchronized void incTxObjects() {
        mTxObjects++;
    }

    public void copyAssets() {

        VisualLog.d("Starting CopyAssets");
        AssetManager assetManager = getAssets();
        String[] files = null;
        try {
            files = assetManager.list(ASSETS_UAVO_INTERNAL_PATH);
        } catch (IOException e) {
            VisualLog.e("Failed to get uavo asset file list.", e);
        }
        if (files != null) {
            for (String filename : files) {
                try {
                    copyFile(assetManager.open(ASSETS_UAVO_INTERNAL_PATH +
                            File.separator + filename), filename);
                } catch (IOException e) {
                    VisualLog.e("Failed to copy uavo asset file: " + filename, e);
                }
            }
        }

        try {
            files = assetManager.list(ASSETS_LOGS_INTERNAL_PATH);
        } catch (IOException e) {
            VisualLog.e("COPY", "Failed to get log asset file list.", e);
        }
        if (files != null) {
            for (String filename : files) {
                try {
                    copyFile(assetManager.open(ASSETS_LOGS_INTERNAL_PATH +
                            File.separator + filename), filename);
                } catch (IOException e) {
                    VisualLog.e("COPY", "Failed to copy log asset file: " + filename, e);
                }
            }
        }
    }

    public void copyFile(InputStream source, String relativeFilename) throws IOException {

        VisualLog.d("Copy " + relativeFilename);

        FileOutputStream out = openFileOutput(ASSETS_UAVO_INTERNAL_PATH +
                getString(R.string.DASH) + relativeFilename, Context.MODE_PRIVATE);

        copyFile(source, out);
        source.close();
        out.flush();
        out.close();
    }

    private void copyFile(InputStream in, OutputStream out) throws IOException {
        byte[] buffer = new byte[1024];
        int read;
        while ((read = in.read(buffer)) != -1) {
            out.write(buffer, 0, read);
        }
    }

    public void initSlider() {
        mTitle = mDrawerTitle = getTitle();

        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerList = (ListView) findViewById(R.id.list_slidermenu);

        try {
            mDrawerListRight = (ListView) findViewById(R.id.list_slidermenu_right);
        } catch (Exception e) {
            e.printStackTrace();
        }

        ArrayList<MenuItem> menuItems = new ArrayList<>();
        ArrayList<MenuItem> menuItemsRight = new ArrayList<>();

        mViewPos = new ArrayList<>();
        mViewPosRight = new ArrayList<>();

        for (ViewController vc : mVcList.values()) {
            menuItems.add(vc.getMenuItem());
            mViewPos.add(vc.getID());
        }

        switch (mCurrentView) {
            case (ViewController.VIEW_P_TUNING): //parent
            case (ViewController.VIEW_PID):      //child
            case (ViewController.VIEW_VPID):     //child
            case (ViewController.VIEW_RESP): {    //child
                for (ViewController vc : mVcList.get(ViewController.VIEW_P_TUNING).getMenuRightItems().values()) {
                    menuItemsRight.add(vc.getMenuItem());
                    mViewPosRight.add(vc.getID());
                }
                break;
            }
            default: {//do nothing
                break;
            }
        }

        MenuListAdapter drawListAdapter = new MenuListAdapter(getApplicationContext(),
                menuItems);
        MenuListAdapter drawListRightAdapter = new MenuListAdapter(getApplicationContext(),
                menuItemsRight);

        mDrawerList.setAdapter(drawListAdapter);
        if (mDrawerListRight != null) {
            mDrawerListRight.setAdapter(drawListRightAdapter);
        }

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        if (toolbar != null) {
            toolbar.setLogo(R.drawable.ic_librepilot_logo_toolbar_48dp);
            toolbar.setNavigationIcon(R.drawable.ic_menu_black_24dp);
        }
        setSupportActionBar(toolbar);
        ActionBar actionbar = getSupportActionBar();
        if (actionbar != null) {
            actionbar.setDisplayHomeAsUpEnabled(true);
            actionbar.setHomeButtonEnabled(true);
            actionbar.setDisplayShowHomeEnabled(true);
        }

        mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
                new Toolbar(this), R.string.APP_NAME, R.string.APP_NAME) {

            public void onDrawerOpened(View drawerView) {
                getSupportActionBar().setTitle(mDrawerTitle);
                invalidateOptionsMenu();
            }

            public void onDrawerClosed(View view) {
                getSupportActionBar().setTitle(mTitle);
                invalidateOptionsMenu();
            }
        };

        mDrawerLayout.removeDrawerListener(mDrawerToggle);
        mDrawerLayout.addDrawerListener(mDrawerToggle);
        SlideMenuClickListener smcl = new SlideMenuClickListener();

        mDrawerList.setOnItemClickListener(smcl);
        if (mDrawerListRight != null) {
            mDrawerListRight.setOnItemClickListener(smcl);
            mDrawerListRight.setOnItemLongClickListener(smcl);
        }
    }

    public void reconnect() {
        mDoReconnect = true;
    }

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

        SettingsHelper.loadSettings(this);

        ViewController cView = mVcList.get(mCurrentView);
        if (cView != null && cView.isChild()) {
            //noop
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(android.view.MenuItem item) {
        if (mDrawerToggle.onOptionsItemSelected(item)) {
            return true;
        }

        switch (item.getItemId()) {
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    @Override
    public void setTitle(CharSequence title) {
        mTitle = title;
        ActionBar ab = this.getSupportActionBar();
        if (ab != null) {
            ab.setTitle(mTitle);
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        VisualLog.setActivity(this);

        mViews = new SparseArray<>(NUM_OF_VIEWS);
        //ViewController mVcDebug =
        //        new ViewControllerDebug(this, R.string.menu_debug, R.drawable.ic_cancel_128dp, View.INVISIBLE, View.INVISIBLE);

        copyAssets();
        SettingsHelper.loadSettings(this);

        mTtsHelper = new TextToSpeechHelper(this);

        //debug view is initialized above
        ViewController mVcTuningParent = new ViewControllerTuningParent(this, R.string.menu_parent_tuning,
                R.drawable.ic_tune_24dp, View.INVISIBLE, View.INVISIBLE);
        //ViewController mVcScope = new ViewControllerScope(this, R.string.menu_scope,
        //        R.drawable.ic_timeline_black_24dp, View.INVISIBLE, View.INVISIBLE);
        ViewController mVcAbout = new ViewControllerAbout(this, R.string.menu_about,
                R.drawable.ic_info_outline_24dp, View.INVISIBLE, View.INVISIBLE);
        ViewController mVcLogs = new ViewControllerLogs(this, R.string.menu_logs,
                R.drawable.ic_rate_review_24dp, View.VISIBLE, View.VISIBLE);
        ViewController mVcSettings = new ViewControllerSettings(this, R.string.menu_settings,
                R.drawable.ic_settings_24dp, View.INVISIBLE, View.INVISIBLE);
        ViewController mVcMap = new ViewControllerMap(this, R.string.menu_map,
                R.drawable.ic_public_24dp, View.INVISIBLE, View.INVISIBLE);
        ViewController mVcObjects = new ViewControllerObjects(this, R.string.menu_objects,
                R.drawable.ic_now_widgets_24dp, View.INVISIBLE, View.INVISIBLE);
        //ViewController mVc3DMagCal = new ViewController3DMagCal(this,
        //        R.string.menu_3dmag, R.drawable.ic_explore_black_24dp, View.VISIBLE, View.INVISIBLE);
        ViewController mVcMain = new ViewControllerMain(this, R.string.menu_main,
                R.drawable.ic_notifications_on_24dp, View.VISIBLE, View.VISIBLE);

        mVcList = new TreeMap<>();
        mVcList.put(ViewController.VIEW_MAIN, mVcMain);
        mVcList.put(ViewController.VIEW_MAP, mVcMap);
        mVcList.put(ViewController.VIEW_OBJECTS, mVcObjects);
        //mVcList.put(ViewController.VIEW_SCOPE, mVcScope);
        mVcList.put(ViewController.VIEW_P_TUNING, mVcTuningParent);
        mVcList.put(ViewController.VIEW_LOGS, mVcLogs);
        mVcList.put(ViewController.VIEW_SETTINGS, mVcSettings);
        mVcList.put(ViewController.VIEW_ABOUT, mVcAbout);
        //mVcList.put(ViewController.VIEW_DEBUG, mVcDebug);
        //mVcList.put(ViewController.VIEW_3DMAG, mVc3DMagCal);

        ((ViewControllerMainAnimatorViewSetter) mVcMain).setLayout();

        initSlider();

        initFirebaseAnalytics();

        logAppOpenEvent();

        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        initWarnDialog().show();
    }

    public boolean initFirebaseAnalytics() {
        if (SettingsHelper.mCollectUsageStatistics) {
            mFirebaseAnalytics = FirebaseAnalytics.getInstance(this);
        } else {
            mFirebaseAnalytics = null;
        }
        return mFirebaseAnalytics != null;
    }

    public void logAppOpenEvent() {
        if (mFirebaseAnalytics != null) {
            Bundle bundle = new Bundle();
            mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.APP_OPEN, bundle);
        }
    }

    public void logViewEvent(ViewController v) {
        if (mFirebaseAnalytics != null && v != null && v.getTitle() != null) {
            final Bundle bundle = new Bundle();
            bundle.putString(FirebaseAnalytics.Param.ITEM_ID,
                    H.trunc(v.getClass().getSimpleName(), 24));
            bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, v.getTitle());
            bundle.putString(FirebaseAnalytics.Param.CONTENT_TYPE,
                    getString(R.string.FBA_CONTENT_TYPE_VIEW));
            mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.SELECT_CONTENT, bundle);
        }
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        mDrawerToggle.syncState();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        mDrawerToggle.onConfigurationChanged(newConfig);
        try {
            mVcList.get(mCurrentView).init();
        } catch (NullPointerException ignored) {
        }

        displayView(mCurrentView);
        initSlider();
    }

    @Override
    protected void onStop() {

        SettingsHelper.saveSettings(this, true);

        if (mFcDevice != null) {
            mFcDevice.setLogging(false);
        }

        if (mPollThread != null) {
            mPollThread.setInvalid();
            mPollThread = null;
        }

        if (mConnectionThread != null) {
            mConnectionThread.setInvalid();
            mConnectionThread = null;
            MainActivity.mHasPThread = false;
        }

        SettingsHelper.mSerialModeUsed = SERIAL_NONE;
        mDoReconnect = true;

        if (mFcDevice != null) {
            mFcDevice.stop();
        }
        mFcDevice = null;

        unregisterReceiver(mUsbReceiver);
        setUsbInterface(null, null);
        mPermissionIntent = null;

        super.onStop();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    public boolean loadXmlObjects(boolean overwrite) {

        if (mXmlObjects == null || (overwrite && SettingsHelper.mLoadedUavo != null)) {
            mXmlObjects = new TreeMap<>();

            String file = SettingsHelper.mLoadedUavo + getString(R.string.UAVO_FILE_EXTENSION);
            ZipInputStream zis = null;
            MessageDigest crypt;
            MessageDigest cumucrypt;
            try {
                InputStream is =
                        openFileInput(ASSETS_UAVO_INTERNAL_PATH + getString(R.string.DASH) + file);
                zis = new ZipInputStream(new BufferedInputStream(is));
                ZipEntry ze;

                //we need to sort the files to generate the correct hash
                SortedMap<String, String> files = new TreeMap<>();

                while ((ze = zis.getNextEntry()) != null) {
                    if (ze.getName().endsWith("xml")) {

                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
                        byte[] buffer = new byte[1024];
                        int count;
                        while ((count = zis.read(buffer)) != -1) {
                            baos.write(buffer, 0, count);
                        }

                        String xml = baos.toString();
                        files.put(ze.getName(), xml);

                        if (xml.length() > 0) {
                            UAVTalkXMLObject obj = new UAVTalkXMLObject(xml);
                            mXmlObjects.put(obj.getName(), obj);
                        }
                    }
                }

                crypt = MessageDigest.getInstance("SHA-1");     //single files hash
                cumucrypt = MessageDigest.getInstance("SHA-1"); //cumulative hash
                cumucrypt.reset();
                for (String xmle : files.values()) {            //cycle over the sorted files
                    crypt.reset();
                    crypt.update(xmle.getBytes());              //hash the file
                    //update a hash over the file hash string representations (yes.)
                    cumucrypt.update(H.bytesToHex(crypt.digest()).toLowerCase().getBytes()); //sic!
                }

                mUavoLongHash = H.bytesToHex(cumucrypt.digest()).toLowerCase();
                VisualLog.d(H.bytesToHex(cumucrypt.digest()).toLowerCase());

            } catch (IOException | SAXException
                    | ParserConfigurationException | NoSuchAlgorithmException e) {
                VisualLog.e("UAVO Load Error", e);
            } finally {
                try {
                    if (zis != null) {
                        zis.close();
                    }
                } catch (IOException e) {
                    VisualLog.e(MessageFormat.format("Exception on Close: {0}", e.getMessage()));
                }
            }
            mDoReconnect = true;

            return true;
        }
        return false;
    }

    protected void connectUSB() {
        try {
            if (SettingsHelper.mSerialModeUsed == SERIAL_USB) {
                for (android.hardware.usb.UsbDevice device : mUsbManager.getDeviceList().values()) {
                    if (device.getDeviceClass() == UsbConstants.USB_CLASS_MISC) {
                        try {
                            mUsbManager.requestPermission(device, mPermissionIntent);
                        } catch (SecurityException e) {
                            SingleToast.show(this,
                                    "USB Security Error. Please try again." + e.getMessage(),
                                    Toast.LENGTH_LONG);
                        }
                    }
                }
            }
        } catch (NullPointerException e) {
            SingleToast.show(this, "Fatal error connecting USB", Toast.LENGTH_LONG);
        }
    }

    protected void connectBluetooth() {
        if (SettingsHelper.mSerialModeUsed == SERIAL_BLUETOOTH) {
            setBluetoothInterface();
        }
    }

    protected void connectLogFile(String filename, FcDevice.GuiEventListener gel) {
        setFileLogInterface(filename, gel);
    }

    public void setContentView(View v, int p) {
        if (mCurrentView != p) {
            mCurrentView = p;
            super.setContentView(v);
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
            initSlider();
        }
    }

    public void displayRightMenuView(ViewController parent, int position) {

        if (mCurrentParentViewController == null) {
            mCurrentParentViewController = parent;
        }

        displayRightMenuView(position);
    }

    public void displayRightMenuView(int position) {
        if (mCurrentParentViewController != null) {
            if (mCurrentParentViewController.getCurrentRightView() == null) {
                mCurrentParentViewController.leave();
            } else {
                mCurrentParentViewController.getCurrentRightView().leave();
            }

            ViewController vcNew = mCurrentParentViewController.getMenuRightItems().get(position);

            mCurrentParentViewController.setCurrentRightView(vcNew);

            for (Map.Entry<Integer, ViewController> e : mCurrentParentViewController.getMenuRightItems().entrySet()) {
                VisualLog.d(MessageFormat.format("{0} {1}", e.getKey(), e.getValue().getTitle()));
            }
            vcNew.enter(position);

            mDrawerListRight.setItemChecked(position, true);
            mDrawerListRight.setSelection(position);

            setToolbarIcons();

            mDrawerLayout.closeDrawer(mDrawerList);
            mDrawerLayout.closeDrawer(mDrawerListRight);
        }
    }

    public void displayView(int position) {
        //clean up current view
        if (mCurrentParentViewController == null) {
            ViewController l = mVcList.get(mCurrentView);
            if (l != null) {
                l.leave();
            }
        } else if (mCurrentParentViewController.getCurrentRightView() != null) {
            mCurrentParentViewController.getCurrentRightView().leave();
        }

        final ViewController vcNew = mVcList.get(position);

        //if (vcNew != null && vcNew.getMenuRightItems() != null && vcNew.getMenuRightItems().size() > 0) {  //if it's > 0, it's a parent
        if (vcNew != null) {
            if (vcNew.isParent()) {
                mCurrentParentViewController = vcNew;
                //} else if(vcNew != null && (vcNew.getMenuRightItems() == null || vcNew.getMenuRightItems().size() == 0)) {
            } else {
                mCurrentParentViewController = null;
            }
            vcNew.enter(position);
        }

        mDrawerList.setItemChecked(position, true);
        mDrawerList.setSelection(position);

        setToolbarIcons();

        mDrawerLayout.closeDrawer(mDrawerList);
    }

    private void setToolbarIcons() {
        imgSerial = (ImageView) findViewById(R.id.imgSerial);
        imgUavoSanity = (ImageView) findViewById(R.id.imgUavoSanity);

        imgGroundTelemetry = (ImageView) findViewById(R.id.imgGroundTelemetry);
        imgFlightTelemetry = (ImageView) findViewById(R.id.imgFlightTelemetry);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        mTtsHelper.onActivityResult(requestCode, resultCode, data);

        mVcList.get(mCurrentView).onActivityResult(requestCode, resultCode, data);
    }

    @Override
    public void onBackPressed() {
        if (mCurrentView == ViewController.VIEW_MAIN) {
            new AlertDialog.Builder(this)
                    .setIcon(R.drawable.ic_warning_black_24dp)
                    .setMessage(R.string.ARE_YOU_SURE_YOU_WANT_TO_CLOSE)
                    .setTitle(R.string.EXIT)
                    .setPositiveButton(R.string.YES, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            finish();
                        }
                    })
                    .setNegativeButton(R.string.NO, null)
                    .show();
        } else {
            displayView(ViewController.VIEW_MAIN);
        }
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_MENU) {
            if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
                mDrawerLayout.closeDrawer(GravityCompat.START);
                try {
                    mDrawerLayout.openDrawer(GravityCompat.END);
                } catch (IllegalArgumentException ignored) {
                }
            } else {
                mDrawerLayout.openDrawer(GravityCompat.START);
                if (mDrawerLayout.isDrawerOpen(GravityCompat.END)) {
                    mDrawerLayout.closeDrawer(GravityCompat.END);
                }
            }
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }

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

        if (mPermissionIntent == null) {
            mPermissionIntent =
                    PendingIntent.getBroadcast(this, 0,
                            new Intent(getString(R.string.ACTION_USB_PERMISSION)), 0);
        }

        mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);

        // listen for new usb devices
        IntentFilter filter = new IntentFilter();
        filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
        filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
        filter.addAction(getString(R.string.ACTION_USB_PERMISSION));

        registerReceiver(mUsbReceiver, filter);

        if (Looper.myLooper() == null) {
            Looper.prepare();
        }

        if (mPollThread == null) {
            mPollThread = new PollThread(this);
            mPollThread.start();
        }
        if (mConnectionThread == null) {
            mConnectionThread = new ConnectionThread(this);
            mConnectionThread.start();
        }

        mDoReconnect = true;

        displayView(mCurrentView);

        VisualLog.d("onStart");
    }

    public void onToolbarFlightSettingsClick(View v) {
        if (mCurrentParentViewController == null) {
            mVcList.get(mCurrentView).onToolbarFlightSettingsClick(v);
        } else {
            mCurrentParentViewController.getMenuRightItems().get(mCurrentView).onToolbarFlightSettingsClick(v);
        }
    }

    public void onToolbarLocalSettingsClick(View v) {
        if (mCurrentParentViewController == null) {
            mVcList.get(mCurrentView).onToolbarLocalSettingsClick(v);
        } else {
            mCurrentParentViewController.getMenuRightItems().get(mCurrentView).onToolbarLocalSettingsClick(v);
        }
    }

    private boolean setFileLogInterface(String filename, FcDevice.GuiEventListener gel) {
        if (mFcDevice != null) {
            mFcDevice.stop();
        }
        mFcDevice = null;
        mFcDevice = new FcLogfileDevice(this, filename, mXmlObjects);
        mFcDevice.setGuiEventListener(gel);
        mFcDevice.start();

        return mFcDevice != null;
    }

    private boolean setBluetoothInterface() {
        if (mFcDevice != null) {
            mFcDevice.stop();
        }
        mFcDevice = null;
        mFcDevice = new FcBluetoothDevice(this, mXmlObjects);
        mFcDevice.start();

        return mFcDevice != null;
    }

    private boolean setUsbInterface(android.hardware.usb.UsbDevice device, UsbInterface intf) {
        if (mDeviceConnection != null) {
            if (mInterface != null) {
                mDeviceConnection.releaseInterface(mInterface);
                mInterface = null;
            }
            mDeviceConnection.close();
            mDevice = null;
            mDeviceConnection = null;
        }

        if (device != null && intf != null) {
            UsbDeviceConnection connection = mUsbManager.openDevice(device);
            if (connection != null) {
                if (connection.claimInterface(intf, true)) {
                    mDevice = device;
                    mDeviceConnection = connection;
                    mInterface = intf;
                    mFcDevice = new FcUsbDevice(this, mDeviceConnection, intf,
                            mXmlObjects);
                    mFcDevice.getObjectTree().setXmlObjects(mXmlObjects);

                    mFcDevice.start();
                    return true;
                } else {
                    connection.close();
                }
            }
        }

        if (mDeviceConnection == null && mFcDevice != null) {
            mFcDevice.stop();
            mFcDevice = null;
        }
        return false;
    }

    private AlertDialog.Builder initWarnDialog() {
        AlertDialog.Builder warnDialogBuilder = new AlertDialog.Builder(this);
        warnDialogBuilder.setTitle(R.string.WARNING);

        warnDialogBuilder.setCancelable(false);

        final TextView info = new TextView(this);
        warnDialogBuilder.setView(info);
        info.setText(R.string.GNU_WARNING);
        info.setPadding(5, 5, 5, 5);

        warnDialogBuilder
                .setPositiveButton(R.string.I_UNDERSTAND, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                        dialog.cancel();
                    }
                });
        return warnDialogBuilder;
    }

    public void onObjectsSanityIndicatorClick(View v) {
        showObjectSanityWarningMessage();
    }

    private void showObjectSanityWarningMessage() {
        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);

        String uavoShortHash = mUavoLongHash != null && mUavoLongHash.length() > 8
                ? mUavoLongHash.substring(0, 8) : getString(R.string.NULL);
        String uavoShortHashFc = mUavoLongHashFc != null && mUavoLongHashFc.length() > 8
                ? mUavoLongHashFc.substring(0, 8) : getString(R.string.NULL);

        if (uavoShortHashFc.equals(getString(R.string.NULL))) {
            dialogBuilder.setTitle(R.string.INFO);
            dialogBuilder.setMessage(
                    getString(R.string.NOT_CONNECTED) + getString(R.string.LF) +
                            getString(R.string.LF) +
                            getString(R.string.UAVO_VERSION_FC) + getString(R.string.TAB) +
                            getString(R.string.TAB) + getString(R.string.TAB) +
                            getString(R.string.TAB) + uavoShortHashFc + getString(R.string.LF) +
                            getString(R.string.UAVO_VERSION) + getString(R.string.TAB) +
                            uavoShortHash
            );
        } else if (uavoShortHashFc.equals(uavoShortHash)) {
            dialogBuilder.setTitle(R.string.INFO);
            dialogBuilder.setMessage(
                    getString(R.string.UAVO_VERSION_OK) + getString(R.string.LF) +
                            getString(R.string.LF) +
                            getString(R.string.UAVO_VERSION_FC) + getString(R.string.TAB) +
                            getString(R.string.TAB) + getString(R.string.TAB) +
                            getString(R.string.TAB) + uavoShortHashFc + getString(R.string.LF) +
                            getString(R.string.UAVO_VERSION) + getString(R.string.TAB) +
                            uavoShortHash
            );
        } else if (uavoShortHashFc.equals(getString(R.string.BAD_FC_UAVO_VERSION))) {
            dialogBuilder.setTitle(R.string.WARNING);
            dialogBuilder.setMessage(
                    getString(R.string.UAVO_MAYBE_TOO_FAST) + getString(R.string.LF) +
                            getString(R.string.LF) +
                            getString(R.string.UAVO_VERSION_FC) + getString(R.string.TAB) +
                            getString(R.string.TAB) + getString(R.string.TAB) +
                            getString(R.string.TAB) + uavoShortHashFc + getString(R.string.LF) +
                            getString(R.string.UAVO_VERSION) + getString(R.string.TAB) +
                            uavoShortHash
            );
        } else {
            dialogBuilder.setTitle(R.string.WARNING);
            dialogBuilder.setMessage(
                    getString(R.string.UAVO_WARNING_A) + getString(R.string.LF) +
                            getString(R.string.UAVO_WARNING_B) + getString(R.string.LF) +
                            getString(R.string.LF) +
                            getString(R.string.UAVO_VERSION_FC) + getString(R.string.TAB) +
                            getString(R.string.TAB) + getString(R.string.TAB) +
                            getString(R.string.TAB) + uavoShortHashFc + getString(R.string.LF) +
                            getString(R.string.UAVO_VERSION) + getString(R.string.TAB) +
                            uavoShortHash
            );
        }

        dialogBuilder.setPositiveButton(R.string.OK_BUTTON,
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    }
                });
        dialogBuilder.show();
    }

    private class SlideMenuClickListener implements ListView.OnItemClickListener, ListView.OnItemLongClickListener {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            switch (parent.getId()) {
                case (R.id.list_slidermenu): {
                    if (mCurrentView != mViewPos.get(position)) {
                        displayView(mViewPos.get(position));
                    } else {
                        if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
                            mDrawerLayout.closeDrawer(GravityCompat.START);
                        }
                    }
                    if (mCurrentParentViewController != null && mCurrentParentViewController.getID() == mViewPos.get(position)) {
                        //clicked on the same left menu item again
                        try {
                            mDrawerLayout.openDrawer(GravityCompat.END);
                        } catch (IllegalArgumentException ignored) {
                        }
                    }
                    break;
                }
                case (R.id.list_slidermenu_right): {
                    displayRightMenuView(mViewPosRight.get(position));
                    break;
                }
                default: //do nothing
                    break;
            }
        }

        @Override
        public boolean onItemLongClick(AdapterView<?> adapterView, View view, int position, long id) {
            if (mCurrentParentViewController != null) {
                mCurrentParentViewController.setFavorite(mViewPosRight.get(position));
                displayRightMenuView(mViewPosRight.get(position));
            }
            return true;
        }
    }
}
 public class VisualLog {

    //private static Activity activity;
    private static String nullstring = "null";
    //private static TextView txtDebugLog;

    static private String checkNull(String msg) {
        return msg == null ? nullstring : msg;
    }

    static public void d(String tag, String msg) {
        Log.d(tag, checkNull(msg));
        printToDebug(tag, checkNull(msg));
    }

    static public void e(String msg) {
        e(new Exception().getStackTrace()[1].getClassName(), msg);
    }

    static public void d(String msg) {
        d(new Exception().getStackTrace()[1].getClassName(), msg);
    }

    static public void d(String tag, String msg, Throwable tr) {
        Log.e(tag, checkNull(msg), tr);
        printToDebug(tag, checkNull(msg));
        printToDebug(tr.getClass().getSimpleName(), Log.getStackTraceString(tr));
    }

    static public void w(String tag, String msg) {
        Log.w(tag, checkNull(msg));
        printToDebug(tag, checkNull(msg));
    }

    static public void w(String msg) {
        w(new Exception().getStackTrace()[1].getClassName(), msg);
    }

    static public void e(String tag, String msg) {
        Log.e(tag, checkNull(msg));
        printToDebug(tag, checkNull(msg));
    }

    static public void e(String tag, String msg, Throwable tr) {
        Log.e(tag, checkNull(msg), tr);
        printToDebug(tag, checkNull(msg));
        printToDebug(tr.getClass().getSimpleName(), Log.getStackTraceString(tr));
    }

    static public void e(String msg, Throwable tr) {
        e(new Exception().getStackTrace()[1].getClassName(), msg, tr);
    }

    static public void e(Throwable tr) {
        VisualLog.e(tr.getClass().getSimpleName(), tr.getMessage(), tr);
    }

    static public void i(String tag, String msg) {
        Log.i(tag, checkNull(msg));
        printToDebug(tag, checkNull(msg));
    }

    static public void i(String msg) {
        i(new Exception().getStackTrace()[1].getClassName(), checkNull(msg));

    }

    static private void printToDebug(final String t, final String s) {
        /*
        activity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (txtDebugLog != null) {
                    txtDebugLog.append(t + " " + s + "\n");
                }
            }
        });
        */
    }

    public static void setActivity(Activity activity) {
        //VisualLog.activity = activity;
        //VisualLog.nullstring = activity.getString(R.string.NULL);
    }

    public static void setDebugLogTextView(TextView txtDebugLog) {
        //VisualLog.txtDebugLog = txtDebugLog;
    }
}
 public class CompatHelper {
    public static void setBackground(View v, Context mActivity, int bg) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            v.setBackground(ContextCompat.getDrawable(mActivity, bg));
        } else {
            //noinspection deprecation
            v.setBackgroundDrawable(ContextCompat.getDrawable(mActivity, bg));
        }
    }
} public class H {

    public final static String NS = ","; //not seperatoir
    public final static String RS = "\\."; //regex reperator
    public final static String S = "."; //seperator
    //from Utils.Crc
    private final static int CRC_TABLE[] = {
            0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15, 0x38, 0x3f, 0x36, 0x31, 0x24, 0x23,
            0x2a, 0x2d,
            0x70, 0x77, 0x7e, 0x79, 0x6c, 0x6b, 0x62, 0x65, 0x48, 0x4f, 0x46, 0x41, 0x54, 0x53,
            0x5a, 0x5d,
            0xe0, 0xe7, 0xee, 0xe9, 0xfc, 0xfb, 0xf2, 0xf5, 0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3,
            0xca, 0xcd,
            0x90, 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85, 0xa8, 0xaf, 0xa6, 0xa1, 0xb4, 0xb3,
            0xba, 0xbd,
            0xc7, 0xc0, 0xc9, 0xce, 0xdb, 0xdc, 0xd5, 0xd2, 0xff, 0xf8, 0xf1, 0xf6, 0xe3, 0xe4,
            0xed, 0xea,
            0xb7, 0xb0, 0xb9, 0xbe, 0xab, 0xac, 0xa5, 0xa2, 0x8f, 0x88, 0x81, 0x86, 0x93, 0x94,
            0x9d, 0x9a,
            0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c, 0x35, 0x32, 0x1f, 0x18, 0x11, 0x16, 0x03, 0x04,
            0x0d, 0x0a,
            0x57, 0x50, 0x59, 0x5e, 0x4b, 0x4c, 0x45, 0x42, 0x6f, 0x68, 0x61, 0x66, 0x73, 0x74,
            0x7d, 0x7a,
            0x89, 0x8e, 0x87, 0x80, 0x95, 0x92, 0x9b, 0x9c, 0xb1, 0xb6, 0xbf, 0xb8, 0xad, 0xaa,
            0xa3, 0xa4,
            0xf9, 0xfe, 0xf7, 0xf0, 0xe5, 0xe2, 0xeb, 0xec, 0xc1, 0xc6, 0xcf, 0xc8, 0xdd, 0xda,
            0xd3, 0xd4,
            0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c, 0x51, 0x56, 0x5f, 0x58, 0x4d, 0x4a,
            0x43, 0x44,
            0x19, 0x1e, 0x17, 0x10, 0x05, 0x02, 0x0b, 0x0c, 0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a,
            0x33, 0x34,
            0x4e, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5c, 0x5b, 0x76, 0x71, 0x78, 0x7f, 0x6a, 0x6d,
            0x64, 0x63,
            0x3e, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2c, 0x2b, 0x06, 0x01, 0x08, 0x0f, 0x1a, 0x1d,
            0x14, 0x13,
            0xae, 0xa9, 0xa0, 0xa7, 0xb2, 0xb5, 0xbc, 0xbb, 0x96, 0x91, 0x98, 0x9f, 0x8a, 0x8d,
            0x84, 0x83,
            0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5, 0xcc, 0xcb, 0xe6, 0xe1, 0xe8, 0xef, 0xfa, 0xfd,
            0xf4, 0xf3
    };
    private final static char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
    private final static int RADIUS = 6371; //the radius of the world.

    public static String intToHex(int i) {
        return bytesToHex(toBytes(i));
    }

    public static int toInt(byte b) {
        return 0xff & b;
    }

    public static int toInt(byte[] bytes) {
        return bytes[0] << 24 | (bytes[1] & 0xFF) << 16 | (bytes[2] & 0xFF) << 8 | (bytes[3] & 0xFF);
    }

    public static long toLong(byte[] bytes) {
        long value = 0;
        for (byte aByte : bytes) {
            value = (value << 8) + (aByte & 0xff);
        }
        return value;
    }

    public static String bytesToHex(byte[] bytes) {
        if (bytes == null) {
            return "null";
        }
        char[] hexChars = new char[bytes.length * 2];
        for (int j = 0; j < bytes.length; j++) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = HEX_ARRAY[v >>> 4];
            hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
        }
        return new String(hexChars);
    }

    public static byte[] reverse4bytes(byte[] b) {
        if (b.length != 4) {
            return null;
        }
        byte[] ret = new byte[4];
        ret[0] = b[3];
        ret[1] = b[2];
        ret[2] = b[1];
        ret[3] = b[0];
        return ret;
    }

    public static byte[] reverse8bytes(byte[] b) {
        if (b.length != 8) {
            return null;
        }
        byte[] ret = new byte[8];
        ret[0] = b[7];
        ret[1] = b[6];
        ret[2] = b[5];
        ret[3] = b[4];
        ret[4] = b[3];
        ret[5] = b[2];
        ret[6] = b[1];
        ret[7] = b[0];
        return ret;
    }

    public static byte[] toReversedBytes(short s) {
        byte[] result = new byte[2];

        result[1] = (byte) (s >> 8);
        result[0] = (byte) (s /*>> 0*/);

        return result;
    }

    public static byte[] toBytes(int i) {
        byte[] result = new byte[4];

        result[0] = (byte) (i >> 24);
        result[1] = (byte) (i >> 16);
        result[2] = (byte) (i >> 8);
        result[3] = (byte) (i /*>> 0*/);

        return result;
    }

    public static byte[] toReversedBytes(int i) {
        byte[] result = new byte[4];

        result[3] = (byte) (i >> 24);
        result[2] = (byte) (i >> 16);
        result[1] = (byte) (i >> 8);
        result[1] = (byte) (i /*>> 0*/);

        return result;
    }

    public static byte[] toBytes(long i) {
        byte[] result = new byte[8];

        result[0] = (byte) (i >> 56);
        result[1] = (byte) (i >> 48);
        result[2] = (byte) (i >> 40);
        result[3] = (byte) (i >> 32);
        result[4] = (byte) (i >> 24);
        result[5] = (byte) (i >> 16);
        result[6] = (byte) (i >> 8);
        result[7] = (byte) (i /*>> 0*/);

        return result;
    }

    /*public static String toHex(byte b) {
        byte[] ba = new byte[1];
        ba[0] = b;
        return bytesToPrintHex(ba);
    }*/

    public static String getDateFromMs(String ms) {
        if (ms == null || ms.equals("")) {
            return "";
        }
        long millis = Math.round(H.stringToFloat(ms));
        return String.format("%02d:%02d:%02d",
                TimeUnit.MILLISECONDS.toHours(millis),
                TimeUnit.MILLISECONDS.toMinutes(millis) -
                        TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(millis)),
                TimeUnit.MILLISECONDS.toSeconds(millis) -
                        TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis)));
    }

    public static String getDateFromSeconds(String seconds) {
        if (seconds == null || seconds.equals("")) {
            return "";
        }
        long millis = Math.round(H.stringToFloat(seconds) * 1000);
        return String.format("%02d:%02d:%02d",
                TimeUnit.MILLISECONDS.toHours(millis),
                TimeUnit.MILLISECONDS.toMinutes(millis) -
                        TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(millis)),
                TimeUnit.MILLISECONDS.toSeconds(millis) -
                        TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis)));
    }

    public static byte[] hexStringToByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                    + Character.digit(s.charAt(i + 1), 16));
        }

        return data;
    }

    //shamelessly stolen from Utils.Crc
    public static int crc8(byte[] b, int start, int len) {
        int crc = 0;
        for (int i = start; i < start + len; i++) {
            crc = (byte) H.CRC_TABLE[((byte) (crc) ^ b[i]) & 0xFF];
        }
        return (crc & 0xFF);
    }

    public static float round(float d, int decimalPlace) {
        BigDecimal bd = new BigDecimal(Float.toString(d));
        bd = bd.setScale(decimalPlace, BigDecimal.ROUND_HALF_UP);
        return bd.floatValue();
    }

    public static double calculationByDistance(LatLng StartP, LatLng EndP) {
        double lat1 = StartP.latitude;
        double lat2 = EndP.latitude;
        double lon1 = StartP.longitude;
        double lon2 = EndP.longitude;

        if (lat1 == lat2 && lon1 == lon2) {
            return .0;
        }

        double dLat = Math.toRadians(lat2 - lat1);
        double dLon = Math.toRadians(lon2 - lon1);
        double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
                + Math.cos(Math.toRadians(lat1))
                * Math.cos(Math.toRadians(lat2)) * Math.sin(dLon / 2)
                * Math.sin(dLon / 2);
        double c = 2 * Math.asin(Math.sqrt(a));
        return RADIUS * c;
    }

    public static byte[] concatArray(byte[] a, byte[] b) {
        int aLen = a.length;
        int bLen = b.length;
        byte[] c = new byte[aLen + bLen];
        System.arraycopy(a, 0, c, 0, aLen);
        System.arraycopy(b, 0, c, aLen, bLen);
        return c;
    }

    public static String k(String s) {
        /*if(s.length() >3) {
            return s.substring(0,s.length()-3) + "k";
        } else {
            return s;
        }*/
        return s;
    }

    public static byte[] floatToByteArrayRev(float value) {
        return H.reverse4bytes(floatToByteArray(value));
    }

    public static byte[] floatToByteArray(float value) {
        return ByteBuffer.allocate(4).putFloat(value).array();
    }

    public static float stringToFloat(String s) {
        if (s == null || s.equals("")) {
            return .0f;
        }
        try {
            return Float.parseFloat(s);
        } catch (NumberFormatException e) {
            VisualLog.d("stringToFloat", "Fallback to Numberformat");
            try {
                return NumberFormat.getInstance().parse(s).floatValue();
            } catch (ParseException e1) {
                e1.printStackTrace();
                return .0f;
            }
        }
    }

    public static String getLogFilename() {
        //OP-2016-03-02_21-04-56
        SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");//dd/MM/yyyy
        Date now = new Date();
        String strDate = sdfDate.format(now);
        return "OP-" + strDate + ".opl";
    }

    public static String trunc(@NonNull final String s, final int l) {
        if (s.length() > l) {
            return s.substring(0, l);
        } else {
            return s;
        }
    }

    public static int dpToPx(int dp, @NonNull Context c) {
        DisplayMetrics displayMetrics = c.getResources().getDisplayMetrics();
        return Math.round(dp * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT));
    }

    public static Bitmap drawableToBitmap(int imgRes, int dpwidth, int dpheight, @NonNull Context c) {
        return drawableToBitmap(ContextCompat.getDrawable(c, imgRes), dpwidth, dpheight, c);
    }

    public static Bitmap drawableToBitmap(Drawable d, int dpwidth, int dpheight, @NonNull Context c) {

        if (d instanceof BitmapDrawable) {
            return ((BitmapDrawable) d).getBitmap();
        }

        int width = H.dpToPx(dpwidth, c);
        int height = H.dpToPx(dpheight, c);

        Bitmap b = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas can = new Canvas(b);
        d.setBounds(0, 0, can.getWidth(), can.getHeight());
        d.draw(can);

        return b;
    }

} public class SettingsHelper {
    public static String mBluetoothDeviceAddress = null;
    public static String mBluetoothDeviceUsed = null;
    public static String mBottomRightLayout;
    public static boolean mColorfulPid = false;
    public static boolean mColorfulVPid = false;
    public static String mLoadedUavo = null;
    public static boolean mLogAsRawUavTalk;
    public static int mSerialModeUsed = -1;
    public static boolean mText2SpeechEnabled = false;
    public static String mTopLeftLayout;
    public static boolean mUseTimestampsFromFc;
    public static Set<String> mObjectFavorites;
    public static int mLogReplaySkipObjects;
    public static boolean mCollectUsageStatistics;
    public static Set<String> mRightMenuFavorites;
    public static boolean mColorfulResp;
    public static float mMagCalBiasX;
    public static float mMagCalBiasY;
    public static float mMagCalBiasZ;
    public static float mMagCalTransformR0C0;
    public static float mMagCalTransformR1C1;
    public static float mMagCalTransformR2C2;
    public static float mAuxMagCalBiasX;
    public static float mAuxMagCalBiasY;
    public static float mAuxMagCalBiasZ;
    public static float mAuxMagCalTransformR0C0;
    public static float mAuxMagCalTransformR1C1;
    public static float mAuxMagCalTransformR2C2;

    @SuppressLint({"CommitPrefEdits", "StringFormatMatches"})
    public static void loadSettings(MainActivity ma) {
        SharedPreferences sharedPref = ma.getPreferences(Context.MODE_PRIVATE);
        mSerialModeUsed = sharedPref.getInt(ma
                .getString(R.string.SETTINGS_SERIAL_MODE, APP_ID), 0);
        mBluetoothDeviceUsed = sharedPref.getString(
                ma.getString(R.string.SETTINGS_BT_NAME, APP_ID), null);
        mBluetoothDeviceAddress = sharedPref.getString(
                ma.getString(R.string.SETTINGS_BT_MAC, APP_ID), null);
        mLoadedUavo = sharedPref
                .getString(ma.getString(R.string.SETTINGS_UAVO_SOURCE, APP_ID),
                        null);
        mColorfulPid = sharedPref.getBoolean(
                ma.getString(R.string.SETTINGS_COLORFUL_PID, APP_ID), false);
        mColorfulVPid = sharedPref.getBoolean(
                ma.getString(R.string.SETTINGS_COLORFUL_VPID, APP_ID),
                false);
        mColorfulResp = sharedPref.getBoolean(
                ma.getString(R.string.SETTINGS_COLORFUL_RESP, APP_ID), false);
        mText2SpeechEnabled = sharedPref.getBoolean(
                ma.getString(R.string.SETTINGS_TEXT2SPEECH_ENABLED, APP_ID),
                false);
        mTopLeftLayout = sharedPref.getString(
                ma.getString(R.string.SETTINGS_TOP_LEFT_LAYOUT_RES, APP_ID),
                ma.getString(R.string.main_element_health));
        mBottomRightLayout = sharedPref.getString(
                ma.getString(R.string.SETTINGS_BOTTOM_RIGHT_LAYOUT_RES, APP_ID),
                ma.getString(R.string.main_element_info));
        mLogAsRawUavTalk = sharedPref.getBoolean(
                ma.getString(R.string.SETTINGS_LOG_RAW, APP_ID),
                false);
        mUseTimestampsFromFc = sharedPref.getBoolean(
                ma.getString(R.string.SETTINGS_TIMESTAMPS_FROM_FC, APP_ID),
                false);
        mLogReplaySkipObjects = sharedPref.getInt(
                ma.getString(R.string.SETTINGS_LOG_REPLAY_SKIP_OBJECTS, APP_ID),
                100);
        mObjectFavorites = sharedPref.getStringSet(
                ma.getString(R.string.SETTINGS_OBJECT_FAVORITES, APP_ID),
                new HashSet<String>());
        mCollectUsageStatistics = sharedPref.getBoolean(
                ma.getString(R.string.SETTINGS_COLLECT_USAGE_STATS, APP_ID),
                false);  //Default for statistics is false, this is opt-in
        mRightMenuFavorites = sharedPref.getStringSet(
                ma.getString(R.string.SETTINGS_RIGHT_MENU_FAVORITES, APP_ID),
                new HashSet<String>());
        mMagCalBiasX = sharedPref.getFloat(
                ma.getString(R.string.SETTINGS_MAGCAL_BIAS_X, APP_ID), .0f);
        mMagCalBiasY = sharedPref.getFloat(
                ma.getString(R.string.SETTINGS_MAGCAL_BIAS_Y, APP_ID), .0f);
        mMagCalBiasZ = sharedPref.getFloat(
                ma.getString(R.string.SETTINGS_MAGCAL_BIAS_Z, APP_ID), .0f);
        mMagCalTransformR0C0 = sharedPref.getFloat(
                ma.getString(R.string.SETTINGS_MAGCAL_TRANSFORM_R0C0, APP_ID), .0f);
        mMagCalTransformR1C1 = sharedPref.getFloat(
                ma.getString(R.string.SETTINGS_MAGCAL_TRANSFORM_R1C1, APP_ID), .0f);
        mMagCalTransformR2C2 = sharedPref.getFloat(
                ma.getString(R.string.SETTINGS_MAGCAL_TRANSFORM_R1C1, APP_ID), .0f);

        mAuxMagCalBiasX = sharedPref.getFloat(
                ma.getString(R.string.SETTINGS_AUX_MAGCAL_BIAS_X, APP_ID), .0f);
        mAuxMagCalBiasY = sharedPref.getFloat(
                ma.getString(R.string.SETTINGS_AUX_MAGCAL_BIAS_Y, APP_ID), .0f);
        mAuxMagCalBiasZ = sharedPref.getFloat(
                ma.getString(R.string.SETTINGS_AUX_MAGCAL_BIAS_Z, APP_ID), .0f);
        mAuxMagCalTransformR0C0 = sharedPref.getFloat(
                ma.getString(R.string.SETTINGS_AUX_MAGCAL_TRANSFORM_R0C0, APP_ID), .0f);
        mAuxMagCalTransformR1C1 = sharedPref.getFloat(
                ma.getString(R.string.SETTINGS_AUX_MAGCAL_TRANSFORM_R1C1, APP_ID), .0f);
        mAuxMagCalTransformR2C2 = sharedPref.getFloat(
                ma.getString(R.string.SETTINGS_AUX_MAGCAL_TRANSFORM_R1C1, APP_ID), .0f);
    }

    @SuppressLint({"CommitPrefEdits", "StringFormatMatches"})
    public static void saveSettings(MainActivity ma, boolean async) {
        SharedPreferences sharedPref = ma.getPreferences(Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = sharedPref.edit();
        editor.clear();

        editor.putString(ma.getString(R.string.SETTINGS_BT_MAC, APP_ID),
                mBluetoothDeviceAddress);
        editor.putString(ma.getString(R.string.SETTINGS_BT_NAME, APP_ID),
                mBluetoothDeviceUsed);
        editor.putString(ma.getString(R.string.SETTINGS_UAVO_SOURCE, APP_ID),
                mLoadedUavo);
        editor.putInt(ma.getString(R.string.SETTINGS_SERIAL_MODE, APP_ID),
                mSerialModeUsed);
        editor.putBoolean(ma.getString(R.string.SETTINGS_COLORFUL_PID, APP_ID),
                SettingsHelper.mColorfulPid);
        editor.putBoolean(ma.getString(R.string.SETTINGS_COLORFUL_VPID, APP_ID),
                SettingsHelper.mColorfulVPid);
        editor.putBoolean(ma.getString(R.string.SETTINGS_COLORFUL_RESP, APP_ID),
                SettingsHelper.mColorfulResp);
        editor.putString(ma.getString(R.string.SETTINGS_TOP_LEFT_LAYOUT_RES,
                APP_ID), mTopLeftLayout);
        editor.putString(ma.getString(R.string.SETTINGS_BOTTOM_RIGHT_LAYOUT_RES,
                APP_ID), mBottomRightLayout);
        editor.putBoolean(ma.getString(R.string.SETTINGS_LOG_RAW, APP_ID),
                SettingsHelper.mLogAsRawUavTalk);
        editor.putBoolean(
                ma.getString(R.string.SETTINGS_TIMESTAMPS_FROM_FC, APP_ID),
                SettingsHelper.mUseTimestampsFromFc);
        editor.putBoolean(
                ma.getString(R.string.SETTINGS_TEXT2SPEECH_ENABLED, APP_ID),
                SettingsHelper.mText2SpeechEnabled);
        editor.putInt(
                ma.getString(R.string.SETTINGS_LOG_REPLAY_SKIP_OBJECTS, APP_ID),
                SettingsHelper.mLogReplaySkipObjects);
        editor.putStringSet(
                ma.getString(R.string.SETTINGS_OBJECT_FAVORITES, APP_ID),
                SettingsHelper.mObjectFavorites);
        editor.putBoolean(
                ma.getString(R.string.SETTINGS_COLLECT_USAGE_STATS, APP_ID),
                SettingsHelper.mCollectUsageStatistics);
        editor.putStringSet(
                ma.getString(R.string.SETTINGS_RIGHT_MENU_FAVORITES, APP_ID),
                SettingsHelper.mRightMenuFavorites);
        editor.putFloat(
                ma.getString(R.string.SETTINGS_MAGCAL_BIAS_X, APP_ID), SettingsHelper.mMagCalBiasX);
        editor.putFloat(
                ma.getString(R.string.SETTINGS_MAGCAL_BIAS_Y, APP_ID), SettingsHelper.mMagCalBiasY);
        editor.putFloat(
                ma.getString(R.string.SETTINGS_MAGCAL_BIAS_Z, APP_ID), SettingsHelper.mMagCalBiasZ);
        editor.putFloat(
                ma.getString(R.string.SETTINGS_MAGCAL_TRANSFORM_R0C0, APP_ID),
                SettingsHelper.mMagCalTransformR0C0);
        editor.putFloat(
                ma.getString(R.string.SETTINGS_MAGCAL_TRANSFORM_R1C1, APP_ID),
                SettingsHelper.mMagCalTransformR1C1);
        editor.putFloat(
                ma.getString(R.string.SETTINGS_MAGCAL_TRANSFORM_R2C2, APP_ID),
                SettingsHelper.mMagCalTransformR2C2);

        editor.putFloat(
                ma.getString(R.string.SETTINGS_AUX_MAGCAL_BIAS_X, APP_ID), SettingsHelper.mAuxMagCalBiasX);
        editor.putFloat(
                ma.getString(R.string.SETTINGS_AUX_MAGCAL_BIAS_Y, APP_ID), SettingsHelper.mAuxMagCalBiasY);
        editor.putFloat(
                ma.getString(R.string.SETTINGS_AUX_MAGCAL_BIAS_Z, APP_ID), SettingsHelper.mAuxMagCalBiasZ);
        editor.putFloat(
                ma.getString(R.string.SETTINGS_AUX_MAGCAL_TRANSFORM_R0C0, APP_ID),
                SettingsHelper.mAuxMagCalTransformR0C0);
        editor.putFloat(
                ma.getString(R.string.SETTINGS_AUX_MAGCAL_TRANSFORM_R1C1, APP_ID),
                SettingsHelper.mAuxMagCalTransformR1C1);
        editor.putFloat(
                ma.getString(R.string.SETTINGS_AUX_MAGCAL_TRANSFORM_R2C2, APP_ID),
                SettingsHelper.mAuxMagCalTransformR2C2);

        if (async) {
            editor.apply();
        } else {
            editor.commit();
        }
        VisualLog.d("SETTINGS", "Saving.... (" + async + ")");
    }

    public static void saveSettings(MainActivity mainActivity) {
        saveSettings(mainActivity, false);
    }

    public static Spinner initSpinner(int spinnerRes, View parent,
                                      AdapterView.OnItemSelectedListener l, int elementRes,
                                      String selection) {
        Spinner spinner = (Spinner) parent.findViewById(spinnerRes);
        ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(parent.getContext(),
                elementRes, android.R.layout.simple_spinner_item);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        spinner.setAdapter(adapter);
        if (l != null) {
            spinner.setOnItemSelectedListener(l);
        }
        spinner.setSelection(adapter.getPosition(selection));
        return spinner;
    }
}
 public class UAVTalkMissingObjectException extends Exception {
    private int mInstance;
    private boolean mIsSettings;
    private String mObjectName;

    public int getInstance() {
        return mInstance;
    }

    public void setInstance(int instance) {
        this.mInstance = instance;
    }

    public String getObjectname() {
        return mObjectName;
    }

    public void setObjectname(String objectname) {
        this.mObjectName = objectname;
    }

    public boolean isSettings() {
        return mIsSettings;
    }

    public void setIsSettings(boolean isSettings) {
        this.mIsSettings = isSettings;
    }
}
 public class UAVTalkObject {

    private final String mId;
    private final HashMap<Integer, UAVTalkObjectInstance> mInstances;
    private UAVTalkObjectListener mListener = null;
    private boolean mWriteBlocked = false;

    private UAVTalkMetaData mMetaData = null;

    public UAVTalkObject(String id) {
        this.mId = id;

        mInstances = new HashMap<>();
    }

    public static byte[] getReqMsg(byte type, String objectId, int instance) {
        byte[] retval = new byte[11];

        retval[0] = 0x3c;
        retval[1] = type;

        retval[2] = 0x0a;
        retval[3] = 0x00;

        byte[] objId = H.hexStringToByteArray(objectId);

        retval[4] = objId[3];
        retval[5] = objId[2];
        retval[6] = objId[1];
        retval[7] = objId[0];

        byte[] iid = H.toBytes(instance);

        retval[8] = iid[3];
        retval[9] = iid[2];

        retval[10] = (byte) (H.crc8(retval, 0, 10) & 0xff);

        return retval;
    }

    public String getId() {
        return mId;
    }

    public HashMap<Integer, UAVTalkObjectInstance> getInstances() {
        return mInstances;
    }

    protected UAVTalkObjectListener getListener() {
        return mListener;
    }

    protected void setListener(UAVTalkObjectListener l) {
        if (mListener == null) {
            this.mListener = l;
        } else {
            throw new IllegalStateException("Listener already set");
        }
    }

    public boolean isWriteBlocked() {
        return mWriteBlocked;
    }

    public void setWriteBlocked(boolean mWriteBlocked) {
        this.mWriteBlocked = mWriteBlocked;
    }

    public void setInstance(UAVTalkObjectInstance instance) {
        mInstances.put(instance.getId(), instance);
        if (getListener() != null) {
            getListener().onObjectUpdate(this);
        }
    }

    protected void removeListener() {
        mListener = null;
    }

    public String toString() {
        return mId + " " + H.bytesToHex(getInstance(0).getData());
    }

    public UAVTalkObjectInstance getInstance(int id) {
        return mInstances.get(id);
    }

    public byte[] toMessage(byte type, int instance, boolean asAck) {
        UAVTalkObjectInstance inst = mInstances.get(instance);

        byte[] instData = null;

        if (inst != null) {
            instData = inst.getData();
        }

        if (instData == null) {
            return new byte[0];
        }

        byte[] retval;

        if (asAck) {
            retval = new byte[11]; //only the header and CRC
        } else {
            retval = new byte[instData.length + 11]; // data as well
            System.arraycopy(instData, 0, retval, 10, instData.length); // copy the data
        }

        retval[0] = 0x3c;
        retval[1] = type;

        byte[] len = H.toBytes(instData.length + 10);
        retval[2] = len[3];
        retval[3] = len[2];

        byte[] objId = H.hexStringToByteArray(this.mId);

        retval[4] = objId[3];
        retval[5] = objId[2];
        retval[6] = objId[1];
        retval[7] = objId[0];

        byte[] iid = H.toBytes(instance);

        retval[8] = iid[3];
        retval[9] = iid[2];

        retval[retval.length - 1] = (byte) (H.crc8(retval, 0, retval.length - 1) & 0xff);

        return retval;
    }
}
 public interface UAVTalkObjectListener {

    void onObjectUpdate(UAVTalkObject o);

}
 @SuppressWarnings("WeakerAccess")
public class UAVTalkXMLObject {

    public static final String FIELDNAME_ENUM = "enum";
    public static final String FIELDNAME_FLOAT32 = "float";
    public static final String FIELDNAME_INT16 = "int16";
    public static final String FIELDNAME_INT32 = "int32";
    public static final String FIELDNAME_INT8 = "int8";
    public static final String FIELDNAME_UINT16 = "uint16";
    public static final String FIELDNAME_UINT32 = "uint32";
    public static final String FIELDNAME_UINT8 = "uint8";
    public static final int FIELDTYPE_ENUM = 7;
    public static final int FIELDTYPE_FLOAT32 = 6;
    public static final int FIELDTYPE_INT16 = 1;
    public static final int FIELDTYPE_INT32 = 2;
    public static final int FIELDTYPE_INT8 = 0;
    public static final int FIELDTYPE_UINT16 = 4;
    public static final int FIELDTYPE_UINT32 = 5;
    public static final int FIELDTYPE_UINT8 = 3;
    final private static String REPLACE_ELEMENT_NODES = "\\r\\n|\\r|\\n| |\\t";
    final private static String REPLACE_OPTION_NODES = "\\r\\n|\\r|\\n| |\\t";
    final private static String XML_ATTRIBUTE_SPLITTER = "\\s*,\\s*";
    private static final String XML_ATT_CATEGORY = "category";
    private static final String XML_ATT_CLONEOF = "cloneof";
    private static final String XML_ATT_ELEMENTNAMES = "elementnames";
    private static final String XML_ATT_ELEMENTS = "elements";
    private static final String XML_ATT_NAME = "name";
    private static final String XML_ATT_OPTIONS = "options";
    private static final String XML_ATT_SETTINGS = "settings";
    private static final String XML_ATT_SINGLEINSTANCE = "singleinstance";
    private static final String XML_ATT_TYPE = "type";
    private static final String XML_TAG_ELEMENTNAMES = "elementnames";
    private static final String XML_TAG_FIELD = "field";
    private static final String XML_TAG_OBJECT = "object";
    private static final String XML_TAG_OPTIONS = "options";
    private static final String XML_TRUE = "true";
    private final boolean DBG = 1 == 0;
    private String mCategory;
    private UAVTalkXMLObjectField[] mFieldArray;
    private int[] mFieldLengths;
    private HashMap<String, UAVTalkXMLObjectField> mFields;
    private int mId;
    private Boolean mIsSettings;
    private Boolean mIsSingleInst;
    private String mName;

    public UAVTalkXMLObject(String xml)
            throws IOException, SAXException, ParserConfigurationException {

        HashMap<String, Integer> fieldNames = new HashMap<>();
        fieldNames.put(FIELDNAME_INT8, FIELDTYPE_INT8);
        fieldNames.put(FIELDNAME_INT16, FIELDTYPE_INT16);
        fieldNames.put(FIELDNAME_INT32, FIELDTYPE_INT32);
        fieldNames.put(FIELDNAME_UINT8, FIELDTYPE_UINT8);
        fieldNames.put(FIELDNAME_UINT16, FIELDTYPE_UINT16);
        fieldNames.put(FIELDNAME_UINT32, FIELDTYPE_UINT32);
        fieldNames.put(FIELDNAME_FLOAT32, FIELDTYPE_FLOAT32);
        fieldNames.put(FIELDNAME_ENUM, FIELDTYPE_ENUM);

        Document doc = loadXMLFromString(xml);

        NodeList objectNodeList = doc.getElementsByTagName(XML_TAG_OBJECT);
        Node objectNode = objectNodeList.item(0);
        Element e = (Element) objectNode;

        try {
            mName = e.getAttribute(XML_ATT_NAME);
            mCategory = e.getAttribute(XML_ATT_CATEGORY);
            mIsSingleInst = e.getAttribute(XML_ATT_SINGLEINSTANCE).equals(XML_TRUE);
            mIsSettings = e.getAttribute(XML_ATT_SETTINGS).equals(XML_TRUE);
        } catch (NullPointerException ex) {
            VisualLog.e("XML", "Attribute Error");
            return;
        }

        NodeList fieldNodeList = doc.getElementsByTagName(XML_TAG_FIELD);
        mFields = new HashMap<>();
        mFieldArray = new UAVTalkXMLObjectField[fieldNodeList.getLength()];
        mFieldLengths = new int[fieldNodeList.getLength()];
        int x = 0;

        for (int i = 0; i < fieldNodeList.getLength(); i++) {
            Node fieldNode = fieldNodeList.item(i);
            UAVTalkXMLObjectField uavField = new UAVTalkXMLObjectField();
            Element f = (Element) fieldNode;

            String sclone = f.getAttribute(XML_ATT_CLONEOF);
            uavField.mName = f.getAttribute(XML_ATT_NAME);

            if (sclone != null && !sclone.equals("")) {
                String tn = uavField.mName;
                uavField = mFields.get(sclone);
                uavField.mName = tn;
            } else {

                uavField.mType = fieldNames.get(f.getAttribute(XML_ATT_TYPE));

                String elementString = f.getAttribute(XML_ATT_ELEMENTNAMES);

                uavField.mElements =
                        new ArrayList<>(
                                Arrays.asList(elementString.split(XML_ATTRIBUTE_SPLITTER)));

                String elementCountString = f.getAttribute(XML_ATT_ELEMENTS);

                if (!elementCountString.equals("") && elementCountString != null) {
                    uavField.mElementCount = Integer.parseInt(elementCountString);
                    if (f.getElementsByTagName(XML_TAG_ELEMENTNAMES).getLength() == 0) {
                        uavField.mElements.clear();
                        for (int j = 0; j < uavField.mElementCount; j++) {
                            uavField.mElements.add(String.valueOf(j));
                        }
                    }
                }

                if (uavField.mType == FIELDTYPE_ENUM) {

                    String optionsString = f.getAttribute(XML_ATT_OPTIONS);
                    try {
                        Object[] oa = Arrays.asList(optionsString.split(XML_ATTRIBUTE_SPLITTER)).toArray();
                        uavField.mOptions = Arrays.copyOf(oa, oa.length, String[].class);
                    } catch (Exception e1) {
                        e1.printStackTrace();
                    }

                    if ((uavField.mOptions == null || uavField.mOptions.length == 0
                            || ((uavField.mOptions.length == 1) &&
                            (uavField.mOptions[0].equals("")))) &&
                            f.getElementsByTagName(XML_TAG_OPTIONS).getLength() > 0) {

                        NodeList optionnodes =
                                f.getElementsByTagName(XML_TAG_OPTIONS).item(0).getChildNodes();

                        ArrayList<String> options = new ArrayList<>();
                        for (int j = 0; j < optionnodes.getLength(); j++) {
                            String content =
                                    optionnodes.item(j).getTextContent()
                                            .replaceAll(REPLACE_OPTION_NODES, "");
                            if (content != null && !content.equals("")) {
                                options.add(content);
                            }
                        }
                        uavField.mOptions = options.toArray(new String[options.size()]);
                    }
                }

                if (f.getElementsByTagName(XML_TAG_ELEMENTNAMES).getLength() > 0) {
                    NodeList elementnodes =
                            f.getElementsByTagName(XML_TAG_ELEMENTNAMES).item(0).getChildNodes();

                    uavField.mElements = new ArrayList<>();
                    for (int j = 0; j < elementnodes.getLength(); j++) {
                        String content = elementnodes.item(j).getTextContent()
                                .replaceAll(REPLACE_ELEMENT_NODES, "");
                        if (content != null && !content.equals("")) {
                            uavField.mElements.add(content);
                        }
                    }
                }

                if (uavField.mElementCount == 0) {
                    uavField.mElementCount = uavField.mElements.size();
                }
            }

            if (uavField.mType == FIELDTYPE_INT8 || uavField.mType == FIELDTYPE_UINT8) {
                mFieldLengths[x] = /*1 * */uavField.mElementCount;
                uavField.mSize = /*1 * */uavField.mElementCount;
                uavField.mTypelength = 1;
            } else if (uavField.mType == FIELDTYPE_INT16 || uavField.mType == FIELDTYPE_UINT16) {
                mFieldLengths[x] = 2 * uavField.mElementCount;
                uavField.mSize = 2 * uavField.mElementCount;
                uavField.mTypelength = 2;
            } else if (uavField.mType == FIELDTYPE_INT32 || uavField.mType == FIELDTYPE_UINT32 ||
                    uavField.mType == FIELDTYPE_FLOAT32) {
                mFieldLengths[x] = 4 * uavField.mElementCount;
                uavField.mSize = 4 * uavField.mElementCount;
                uavField.mTypelength = 4;
            } else if (uavField.mType == FIELDTYPE_ENUM) {
                mFieldLengths[x] = uavField.mElementCount;
                uavField.mSize = uavField.mElementCount;
                uavField.mTypelength = 1;
            }
            x++;

            mFields.put(uavField.mName, uavField);
            mFieldArray[i] = uavField;
        }

        Arrays.sort(mFieldArray);
        mFields.clear(); //TODO:This is crappy code!
        int j = 0;
        for (UAVTalkXMLObjectField xuav : mFieldArray) {
            xuav.mPos = j;     //set new position
            j += xuav.mTypelength * xuav.mElementCount;
            mFields.put(xuav.mName, xuav);
        }

        this.mId = calculateID();
    }

    private static Document loadXMLFromString(String xml)
            throws ParserConfigurationException, IOException, SAXException {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        InputSource is = new InputSource(new StringReader(xml));
        return builder.parse(is);
    }

    public String getCategory() {
        return mCategory;
    }

    public HashMap<String, UAVTalkXMLObjectField> getFields() {
        return mFields;
    }

    public String getId() {
        return H.bytesToHex(H.toBytes(mId));
    }

    public int getLength() {
        int retval = 0;
        for (int i : mFieldLengths) {
            retval += i;
        }
        return retval;
    }

    public String getName() {
        return mName;
    }

    public Boolean isSettings() {
        return mIsSettings;
    }

    private int calculateID() {
        // Hash object name
        if (DBG) {
            VisualLog.d("HASH 1 ", " ");
        }
        if (DBG) {
            VisualLog.d("HASH 2 ", this.mName);
        }
        int hash = updateHash(this.mName, 0);
        // Hash object attributes
        hash = updateHash(this.mIsSettings ? 1 : 0, hash);
        hash = updateHash(this.mIsSingleInst ? 1 : 0, hash);
        // Hash field information
        for (UAVTalkXMLObjectField aMFieldArray : this.mFieldArray) {
            if (DBG) {
                VisualLog.d("HASH 3 ", aMFieldArray.mName);
            }
            hash = updateHash(aMFieldArray.mName, hash);
            hash = updateHash((aMFieldArray.mElementCount), hash);
            hash = updateHash(aMFieldArray.mType, hash);
            if (aMFieldArray.mType == FIELDTYPE_ENUM) {
                String[] options = aMFieldArray.mOptions;
                if (DBG) {
                    VisualLog.d("HASH 6 ", "" + (options == null));
                }
                for (String option : options) {
                    if (DBG) {
                        VisualLog.d("HASH 4 ", option);
                    }
                    hash = updateHash(option, hash);
                }
            }
        }
        return hash & 0xFFFFFFFE;
    }

    private int updateHash(int value, int hash) {
        int ret = hash ^ ((hash << 5) + (hash >>> 2) + value);
        if (DBG) {
            long y = ret & 0x00000000ffffffffL;
            long in = value & 0x00000000ffffffffL;
            VisualLog.d("HASH 5 ", "" + in + "=>" + y);
        }
        return ret;
    }

    /**
     * Update the hash given a string
     */
    private int updateHash(String value, int hash) {
        byte[] bytes = value.getBytes();
        int hashout = hash;
        for (byte aByte : bytes) {
            hashout = updateHash(aByte, hashout);
        }

        return hashout;
    }

    //TODO: Getter and Setter
    public class UAVTalkXMLObjectField implements Comparable<UAVTalkXMLObjectField> {
        int mElementCount;
        ArrayList<String> mElements;
        String mName;
        String[] mOptions;
        int mPos;
        int mSize;
        int mType;
        int mTypelength;

        public ArrayList<String> getElements() {
            return mElements;
        }

        public String getName() {
            return mName;
        }

        public String[] getOptions() {
            return mOptions;
        }

        public int getType() {
            return mType;
        }

        @Override
        public int compareTo(UAVTalkXMLObjectField another) {
            return another.mTypelength - mTypelength;
        }

        public String toString() {
            return mName + " Pos: " + mPos + " ElementCount: " + mElementCount + " Size:" + mSize +
                    " Type:" + mType;
        }
    }
}
 public class SingleToast {

    private static Toast mToast;

    public static void show(Context context, String text, int duration) {
        VisualLog.i("Toast", text);
        if (mToast != null) {
            mToast.cancel();
        }
        try {
            mToast = Toast.makeText(context, text, duration);
        } catch (RuntimeException e) {
            VisualLog.e("SINGLETOAST", e.getMessage(), e);
        }
        mToast.show();
    }

    public static void show(Context context, int text, int duration) {
        VisualLog.i("Toast", context.getString(text));
        if (mToast != null) {
            mToast.cancel();
        }
        mToast = Toast.makeText(context, text, duration);
        mToast.show();
    }

    public static void show(Context context, String text) {
        show(context, text, Toast.LENGTH_LONG);
    }
}
 public class EnumInputAlertDialog extends InputAlertDialog {

    private int mChoice = -1;

    public EnumInputAlertDialog(Context parent) {
        super(parent);
    }

    public void show() {
        if (mFcDevice == null) {
            SingleToast.show(getContext(), getContext().getString(R.string.NOT_CONNECTED),
                    Toast.LENGTH_SHORT);
            return;
        }

        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getContext());
        dialogBuilder.setTitle(mTitle);

        String[] types;
        try {
            types = mFcDevice.getObjectTree().getXmlObjects().get(mObject).getFields()
                    .get(mField).getOptions();
        } catch (NullPointerException e) {
            types = null;
        }


        int current = 0;
        try {
            String type =
                    mFcDevice.getObjectTree().getData(mObject, 0, mField, mElement).toString();
            if (type != null && types != null) {
                for (String t : types) {
                    if (t.equals(type)) {
                        break;
                    }
                    current++;
                }
            }
        } catch (UAVTalkMissingObjectException | NumberFormatException | NullPointerException ignored) {
            ignored.printStackTrace();
        }

        dialogBuilder.setSingleChoiceItems(types, current, new DialogInterface.OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int which) {
                mChoice = which;
            }
        });

        dialogBuilder.setPositiveButton(R.string.SAVE_BUTTON,
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        if (mChoice >= 0) {
                            process(mChoice);
                            mFcDevice.savePersistent(mObject);
                        }

                        dialog.dismiss();
                    }
                });
        dialogBuilder.setNeutralButton(R.string.UPLOAD_BUTTON,
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        if (mChoice >= 0) {
                            process(mChoice);
                        }
                        dialog.dismiss();
                    }
                });

        dialogBuilder.setNegativeButton(R.string.CANCEL_BUTTON,
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.cancel();
                        dialog.dismiss();
                    }
                });

        dialogBuilder.show();
    }

    private void process(int which) {

        byte[] send = new byte[1];
        send[0] = (byte) which;
        if (mFcDevice != null) {
            mFcDevice.sendSettingsObject(mObject, 0, mField, mElement, send);
            //mUAVTalkDevice.savePersistent("RevoSettings");
        }
    }
}
 public class NumberInputAlertDialog extends InputAlertDialog implements TextWatcher {

    private static String str = "";

    public NumberInputAlertDialog(Context parent) {
        super(parent);
    }

    public void show() {
        if (mFcDevice == null) {
            SingleToast.show(getContext(), getContext().getString(R.string.NOT_CONNECTED),
                    Toast.LENGTH_SHORT);
            return;
        }
        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getContext());
        dialogBuilder.setTitle(mTitle);

        final View alertView = View.inflate(getContext(), mLayout, null);
        dialogBuilder.setView(alertView);

        final EditText input = (EditText) alertView.findViewById(R.id.etxInput);

        long min = -1;
        long max = -1;

        //max/min values are fallback default if there are none set with .withMinMax
        //.setInputType will block "-" and "."/"," if not appripriate
        switch (mFieldType) {
            case UAVTalkXMLObject.FIELDTYPE_ENUM:
                throw new UnsupportedOperationException();
                //break;
            case UAVTalkXMLObject.FIELDTYPE_UINT8:
                min = 0;
                max = 255;
                input.setInputType(InputType.TYPE_CLASS_NUMBER);
                break;
            case UAVTalkXMLObject.FIELDTYPE_UINT16:
                min = 0;
                max = 65535;
                input.setInputType(InputType.TYPE_CLASS_NUMBER);
                break;
            case UAVTalkXMLObject.FIELDTYPE_UINT32:
                min = 0;
                max = 4294967295L;
                input.setInputType(InputType.TYPE_CLASS_NUMBER);
                break;
            case UAVTalkXMLObject.FIELDTYPE_INT8:
                min = -128;
                max = 127;
                input.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
                break;
            case UAVTalkXMLObject.FIELDTYPE_INT16:
                min = -32768;
                max = 32767;
                input.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
                break;
            case UAVTalkXMLObject.FIELDTYPE_INT32:
                min = -2147483648;
                max = 2147483647;
                input.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
                break;
            case UAVTalkXMLObject.FIELDTYPE_FLOAT32:
                min = -(int) Float.MAX_VALUE; //don't get academic on this.
                max = (long) Float.MAX_VALUE;

                input.setInputType(
                        InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
                input.setKeyListener(DigitsKeyListener.getInstance("0123456789" + H.S));
                break;
            default:
                SingleToast.show(getContext(), "Type not implemented", Toast.LENGTH_SHORT);
                break;
        }

        if (this.mMin == -1 && this.mMax == -1) {
            mMin = min;
            mMax = max;
        }

        String fText;
        if (mFieldType == UAVTalkXMLObject.FIELDTYPE_FLOAT32) {
            fText = (new DecimalFormat("##########.#######")).format(H.stringToFloat(mText));
            input.addTextChangedListener(this);
            fText = fText.replace(H.NS, H.S);
        } else {
            fText = mText;
        }


        input.setText(fText);
        input.setSelection(0);
        input.requestFocus();
        if (mMin >= 0 && mMax >= 0) {
            input.setFilters(new InputFilter[]{new InputFilterMinMax(getContext(), mMin, mMax)});
        }

        dialogBuilder.setPositiveButton(R.string.SAVE_BUTTON,
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        process(input.getText().toString());

                        mFcDevice.savePersistent(mObject);

                        dialog.dismiss();
                    }
                });
        dialogBuilder.setNeutralButton(R.string.UPLOAD_BUTTON,
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        process(input.getText().toString());
                    }
                });

        dialogBuilder.setNegativeButton(R.string.CANCEL_BUTTON,
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.cancel();
                        dialog.dismiss();
                    }
                });
        dialogBuilder.show();
    }

    private void process(String input) {

        byte[] data;
        try {
            switch (mFieldType) {
                case UAVTalkXMLObject.FIELDTYPE_UINT8: {
                    data = new byte[1];
                    int i = Integer.parseInt(input);
                    data[0] = H.toBytes(i)[3]; //want the lsb
                    break;
                }
                case UAVTalkXMLObject.FIELDTYPE_INT8: {
                    data = new byte[1];
                    int i = Integer.parseInt(input);
                    byte b = (byte) i;
                    data[0] = b;
                    break;
                }
                case UAVTalkXMLObject.FIELDTYPE_UINT16: {
                    data = new byte[2];
                    int i = Integer.parseInt(input);
                    byte[] temp = H.toBytes(i);
                    data[0] = temp[3];
                    data[1] = temp[2];
                    break;
                }
                case UAVTalkXMLObject.FIELDTYPE_INT16: {
                    int i = Integer.parseInt(input);
                    short s = (short) i;
                    data = H.toReversedBytes(s);
                    break;
                }
                case UAVTalkXMLObject.FIELDTYPE_UINT32: {
                    data = new byte[4];
                    long l = Long.parseLong(input) & 0xffffff;  //remove higher 4 bytes
                    byte[] temp = H.toBytes(l); //8 bytes
                    data[3] = temp[4];  //TODO:check if that's correct
                    data[2] = temp[5];
                    data[1] = temp[6];
                    data[0] = temp[7];
                    break;
                }
                case UAVTalkXMLObject.FIELDTYPE_INT32: {
                    data = H.toReversedBytes(Integer.parseInt(input));
                    break;
                }
                case UAVTalkXMLObject.FIELDTYPE_FLOAT32: {
                    data = H.reverse4bytes(H.floatToByteArray(Float.parseFloat(input)));
                    break;
                }
                default:
                    data = new byte[0];
                    SingleToast.show(getContext(), "Type not yet implemented!", Toast.LENGTH_SHORT);
                    VisualLog.e("NumberInputAlertDialog",
                            "Type not implemented! " + mFieldType + " " + input + " " +
                                    Arrays.toString(H.toBytes(Integer.parseInt(input))));
                    break;
            }

        } catch (NumberFormatException e) {
            e.printStackTrace();
            data = H.toBytes(0);
        }


        if (data != null && mFcDevice != null && data.length > 0) {
            VisualLog.d("IntegerInputDialog",
                    "" + mFieldType + " " + input + " " + H.bytesToHex(data));
            mFcDevice.sendSettingsObject(mObject, 0, mField, mElement, data);
        }
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        str = s.toString();
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        //do nothing
    }

    @Override
    public void afterTextChanged(Editable s) {
        String ls = s.toString();
        if (ls.length() > 0 && !Pattern.matches("^\\d*" + H.RS + "?\\d*$", ls)) {
            s.replace(0, s.length(), str);
        }
    }
}
 public class PfdRollPitchView extends View {

    final int VIEWPORT_HEIGHT_DIVISOR = 45;
    private final Rect textBounds = new Rect();
    private boolean isInitialized;
    private Path mDashedPitchPath;
    private Paint mEarthPaint;
    private Rect mEarthRect;
    private Paint mFatHorizonPaint;
    private int mHalfDiag;
    private int mHorizon;
    private float mHorizonDivisor;
    private Paint mHorizonPaint;
    private Rect mHorizonRect;
    private int mMiddle;
    private int mPitch;
    private int mRadius;
    private float mRawPitch;
    private float mRoll;
    private Path mRollScaleBottomTrianglePath;
    private Paint mRollScaleCrescentPaint;
    private RectF mRollScaleOval;
    private Paint mRollScalePaint;
    private PointF mRollScaleTick225EndPoint;
    private PointF mRollScaleTick225StartPoint;
    private PointF mRollScaleTick240EndPoint;
    private PointF mRollScaleTick240StartPoint;
    private PointF mRollScaleTick245EndPoint;
    private PointF mRollScaleTick245StartPoint;
    private PointF mRollScaleTick250EndPoint;
    private PointF mRollScaleTick250StartPoint;
    private PointF mRollScaleTick255EndPoint;
    private PointF mRollScaleTick255StartPoint;
    private PointF mRollScaleTick260EndPoint;
    private PointF mRollScaleTick260StartPoint;
    private PointF mRollScaleTick265EndPoint;
    private PointF mRollScaleTick265StartPoint;
    private PointF mRollScaleTick275EndPoint;
    private PointF mRollScaleTick275StartPoint;
    private PointF mRollScaleTick280EndPoint;
    private PointF mRollScaleTick280StartPoint;
    private PointF mRollScaleTick285EndPoint;
    private PointF mRollScaleTick285StartPoint;
    private PointF mRollScaleTick290EndPoint;
    private PointF mRollScaleTick290StartPoint;
    private PointF mRollScaleTick295EndPoint;
    private PointF mRollScaleTick295StartPoint;
    private PointF mRollScaleTick300EndPoint;
    private PointF mRollScaleTick300StartPoint;
    private PointF mRollScaleTick315EndPoint;
    private PointF mRollScaleTick315StartPoint;
    private Path mRollScaleTopTrianglePath;
    private Paint mSkyPaint;
    private Rect mSkyRect;
    private Paint mUiPaint;
    private Paint mUiStrokedPaint;
    private Path mStaticTriangleBrightPath;
    private Paint mStaticHorizonBrightPaint;
    private Paint mStaticHorizonDarkPaint;
    private Path mStaticTriangleDarkPath;
    private Path mStaticLeftBarBrightPath;
    private Path mStaticLeftBarDarkPath;
    private Path mStaticRightBarBrightPath;
    private Path mStaticRightBarDarkPath;

    public PfdRollPitchView(Context context) {
        super(context);
    }

    public PfdRollPitchView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public PfdRollPitchView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void setPitch(float pitch) {
        this.mRawPitch = pitch;
        this.mPitch = Math.round(pitch * (mHorizon / VIEWPORT_HEIGHT_DIVISOR));
    }

    public void setRoll(float roll) {
        this.mRoll = -roll;
    }

    private void init() {
        //the length are just lazyness to do the real math, will calculate this stuff correctly later.
        //maybe.

        mHorizon = Math.round(this.getHeight() / 2f + .5f);
        mMiddle = Math.round(this.getWidth() / 2f + .5f);

        final long diag = Math.round(Math.sqrt(Math.pow(mHorizon, 2) + Math.pow(mMiddle, 2)));

        mHalfDiag = Math.round(diag / 2);
        mHorizonDivisor = mHorizon / VIEWPORT_HEIGHT_DIVISOR;

        setPitch(0);
        setRoll(0);

        //draw earth
        mEarthRect = new Rect(-mHalfDiag, mHorizon + mPitch, mHalfDiag * 4, mHalfDiag * 4);
        mEarthPaint = new Paint();
        mEarthPaint.setColor(Color.argb(0xff, 0x65, 0x33, 0x00));

        //draw sky
        mSkyRect = new Rect(-mHalfDiag, -mHalfDiag, mHalfDiag * 4, mHorizon + mPitch);
        mSkyPaint = new Paint();
        mSkyPaint.setColor(Color.argb(0xff, 0x01, 0x64, 0xcc));

        //draw horizon
        mHorizonRect = new Rect(-mHalfDiag, (mHorizon - 2) + mPitch, mHalfDiag * 4,
                (mHorizon + 2) + mPitch);
        mHorizonPaint = new Paint();
        mHorizonPaint.setColor(Color.argb(0xff, 0xff, 0xff, 0xff));

        mUiPaint = new Paint();
        mUiPaint.setColor(Color.WHITE);
        mUiPaint.setStrokeWidth(5);
        mUiPaint.setTextSize(mHorizon / 17);

        mUiStrokedPaint = new Paint();
        mUiStrokedPaint.setColor(Color.WHITE);
        mUiStrokedPaint.setStrokeWidth(5);
        mUiStrokedPaint.setStyle(Paint.Style.STROKE);
        mUiStrokedPaint.setPathEffect(new DashPathEffect(new float[]{10, 2}, 0));

        //draw RollScale
        mRadius = Math.round(mHorizon / 2);

        mRollScaleCrescentPaint = new Paint();
        mRollScaleCrescentPaint.setColor(Color.WHITE);
        mRollScaleCrescentPaint.setStrokeWidth(4);
        mRollScaleCrescentPaint.setStyle(Paint.Style.STROKE);

        //rollscale half circle
        mRollScaleOval = new RectF();
        mRollScaleOval.set(mMiddle - mRadius, mHorizon - mRadius,
                mMiddle + mRadius, mHorizon + mRadius);

        //rollscale center triangle
        mRollScalePaint = new Paint();
        mRollScalePaint.setColor(Color.WHITE);
        mRollScalePaint.setStrokeWidth(4);
        mRollScalePaint.setStyle(Paint.Style.FILL);

        mRollScaleTopTrianglePath = new Path();
        mRollScaleTopTrianglePath.moveTo(mMiddle, mHorizon - mRadius);
        mRollScaleTopTrianglePath
                .lineTo(mMiddle - (mMiddle * .04f), mHorizon - mRadius - (mMiddle * .06f));
        mRollScaleTopTrianglePath
                .lineTo(mMiddle + (mMiddle * .04f), mHorizon - mRadius - (mMiddle * .06f));
        mRollScaleTopTrianglePath.lineTo(mMiddle, mHorizon - mRadius);

        //ticks
        float bigTickHeight = mMiddle * 0.06f;
        float smallTickHeight = mMiddle * 0.03f;

        mRollScaleTick225StartPoint = getPosition(mHorizon, mMiddle, mRadius, 225);
        mRollScaleTick225EndPoint = getPosition(mHorizon, mMiddle, mRadius + bigTickHeight, 225);

        mRollScaleTick240StartPoint = getPosition(mHorizon, mMiddle, mRadius, 240);
        mRollScaleTick240EndPoint = getPosition(mHorizon, mMiddle, mRadius + bigTickHeight, 240);

        mRollScaleTick245StartPoint = getPosition(mHorizon, mMiddle, mRadius, 245);
        mRollScaleTick245EndPoint = getPosition(mHorizon, mMiddle, mRadius + smallTickHeight, 245);

        mRollScaleTick250StartPoint = getPosition(mHorizon, mMiddle, mRadius, 250);
        mRollScaleTick250EndPoint = getPosition(mHorizon, mMiddle, mRadius + smallTickHeight, 250);

        mRollScaleTick255StartPoint = getPosition(mHorizon, mMiddle, mRadius, 255);
        mRollScaleTick255EndPoint = getPosition(mHorizon, mMiddle, mRadius + bigTickHeight, 255);

        mRollScaleTick260StartPoint = getPosition(mHorizon, mMiddle, mRadius, 260);
        mRollScaleTick260EndPoint = getPosition(mHorizon, mMiddle, mRadius + smallTickHeight, 260);

        mRollScaleTick265StartPoint = getPosition(mHorizon, mMiddle, mRadius, 265);
        mRollScaleTick265EndPoint = getPosition(mHorizon, mMiddle, mRadius + smallTickHeight, 265);

        //on 270 is the triangle

        mRollScaleTick275StartPoint = getPosition(mHorizon, mMiddle, mRadius, 275);
        mRollScaleTick275EndPoint = getPosition(mHorizon, mMiddle, mRadius + smallTickHeight, 275);

        mRollScaleTick280StartPoint = getPosition(mHorizon, mMiddle, mRadius, 280);
        mRollScaleTick280EndPoint = getPosition(mHorizon, mMiddle, mRadius + smallTickHeight, 280);

        mRollScaleTick285StartPoint = getPosition(mHorizon, mMiddle, mRadius, 285);
        mRollScaleTick285EndPoint = getPosition(mHorizon, mMiddle, mRadius + bigTickHeight, 285);

        mRollScaleTick290StartPoint = getPosition(mHorizon, mMiddle, mRadius, 290);
        mRollScaleTick290EndPoint = getPosition(mHorizon, mMiddle, mRadius + smallTickHeight, 290);

        mRollScaleTick295StartPoint = getPosition(mHorizon, mMiddle, mRadius, 295);
        mRollScaleTick295EndPoint = getPosition(mHorizon, mMiddle, mRadius + smallTickHeight, 295);

        mRollScaleTick300StartPoint = getPosition(mHorizon, mMiddle, mRadius, 300);
        mRollScaleTick300EndPoint = getPosition(mHorizon, mMiddle, mRadius + bigTickHeight, 300);

        mRollScaleTick315StartPoint = getPosition(mHorizon, mMiddle, mRadius, 315);
        mRollScaleTick315EndPoint = getPosition(mHorizon, mMiddle, mRadius + bigTickHeight, 315);

        //rollscale bottom triangle
        mRollScaleBottomTrianglePath = new Path();
        mRollScaleBottomTrianglePath.moveTo(mMiddle, mHorizon - mRadius);
        mRollScaleBottomTrianglePath
                .lineTo(mMiddle - (mMiddle * .02f), mHorizon - mRadius + (mMiddle * .04f));
        mRollScaleBottomTrianglePath
                .lineTo(mMiddle + (mMiddle * .02f), mHorizon - mRadius + (mMiddle * .04f));
        mRollScaleBottomTrianglePath.lineTo(mMiddle, mHorizon - mRadius);

        //fat horizon line
        mFatHorizonPaint = new Paint();
        mFatHorizonPaint.setColor(Color.argb(0xff, 0xff, 0xff, 0xff));
        mFatHorizonPaint.setStrokeWidth(8);

        mDashedPitchPath = new Path();

        //bright yellow triangle
        mStaticHorizonBrightPaint = new Paint();
        mStaticHorizonBrightPaint.setColor(Color.argb(0xff, 0xff, 0xff, 0x0));
        mStaticHorizonBrightPaint.setStrokeWidth(8);

        mStaticTriangleBrightPath = new Path();
        mStaticTriangleBrightPath.moveTo(mMiddle, mHorizon);
        mStaticTriangleBrightPath.lineTo(mMiddle - (mMiddle * .15f), mHorizon + (mMiddle * .1f));
        mStaticTriangleBrightPath.lineTo(mMiddle, mHorizon + (mMiddle * .035f));
        mStaticTriangleBrightPath.lineTo(mMiddle + (mMiddle * .15f), mHorizon + (mMiddle * .1f));
        mStaticTriangleBrightPath.lineTo(mMiddle, mHorizon);

        mStaticLeftBarBrightPath = new Path();
        mStaticLeftBarBrightPath.moveTo(mMiddle - mRadius * .55f, mHorizon);
        mStaticLeftBarBrightPath.lineTo(mMiddle - mRadius * .6f, mHorizon * 0.98f);
        mStaticLeftBarBrightPath.lineTo(mMiddle - mRadius, mHorizon * 0.98f);
        mStaticLeftBarBrightPath.lineTo(mMiddle - mRadius, mHorizon);
        mStaticLeftBarBrightPath.lineTo(mMiddle - mRadius * .55f, mHorizon);

        mStaticLeftBarDarkPath = new Path();
        mStaticLeftBarDarkPath.moveTo(mMiddle - mRadius * .55f, mHorizon);
        mStaticLeftBarDarkPath.lineTo(mMiddle - mRadius * .6f, mHorizon * 1.02f);
        mStaticLeftBarDarkPath.lineTo(mMiddle - mRadius, mHorizon * 1.02f);
        mStaticLeftBarDarkPath.lineTo(mMiddle - mRadius, mHorizon);
        mStaticLeftBarDarkPath.lineTo(mMiddle - mRadius * .55f, mHorizon);

        //dark yellow triangle
        mStaticHorizonDarkPaint = new Paint();
        mStaticHorizonDarkPaint.setColor(Color.argb(0xff, 0x80, 0x80, 0x0));
        mStaticHorizonDarkPaint.setStrokeWidth(8);

        mStaticTriangleDarkPath = new Path();
        mStaticTriangleDarkPath.moveTo(mMiddle, mHorizon + (mMiddle * .035f));
        mStaticTriangleDarkPath.lineTo(mMiddle - (mMiddle * .15f), mHorizon + (mMiddle * .1f));
        mStaticTriangleDarkPath.lineTo(mMiddle, mHorizon + (mMiddle * .06f));
        mStaticTriangleDarkPath.lineTo(mMiddle + (mMiddle * .15f), mHorizon + (mMiddle * .1f));
        mStaticTriangleDarkPath.lineTo(mMiddle, mHorizon + (mMiddle * .035f));

        mStaticRightBarBrightPath = new Path();
        mStaticRightBarBrightPath.moveTo(mMiddle + mRadius * .55f, mHorizon);
        mStaticRightBarBrightPath.lineTo(mMiddle + mRadius * .6f, mHorizon * 0.98f);
        mStaticRightBarBrightPath.lineTo(mMiddle + mRadius, mHorizon * 0.98f);
        mStaticRightBarBrightPath.lineTo(mMiddle + mRadius, mHorizon);
        mStaticRightBarBrightPath.lineTo(mMiddle + mRadius * .55f, mHorizon);

        mStaticRightBarDarkPath = new Path();
        mStaticRightBarDarkPath.moveTo(mMiddle + mRadius * .55f, mHorizon);
        mStaticRightBarDarkPath.lineTo(mMiddle + mRadius * .6f, mHorizon * 1.02f);
        mStaticRightBarDarkPath.lineTo(mMiddle + mRadius, mHorizon * 1.02f);
        mStaticRightBarDarkPath.lineTo(mMiddle + mRadius, mHorizon);
        mStaticRightBarDarkPath.lineTo(mMiddle + mRadius * .55f, mHorizon);

        isInitialized = true;
    }

    private PointF getPosition(int cy, int cx, float rad, float angle) {
        return new PointF((float) (cx + rad * Math.cos(Math.toRadians(angle))),
                (float) (cy + rad * Math.sin(Math.toRadians(angle))));
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        init();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (!isInitialized) {
            init();
        }

        canvas.save();
        canvas.rotate(mRoll, mMiddle, mHorizon);

        //everything that pitches and rolls
        mSkyRect.set(-mHalfDiag, -mHalfDiag, mHalfDiag * 4, mHorizon + mPitch);
        canvas.drawRect(mSkyRect, mSkyPaint);

        mEarthRect.set(-mHalfDiag, mHorizon + mPitch, mHalfDiag * 4, mHalfDiag * 4);
        canvas.drawRect(mEarthRect, mEarthPaint);

        mHorizonRect
                .set(-mHalfDiag, (mHorizon - 2) + mPitch, mHalfDiag * 4, (mHorizon + 2) + mPitch);
        canvas.drawRect(mHorizonRect, mHorizonPaint);

        //everything that just rolls
        canvas.drawArc(mRollScaleOval, 225, 90, false, mRollScaleCrescentPaint);

        canvas.drawPath(mRollScaleTopTrianglePath, mRollScalePaint);

        canvas.drawLine(mRollScaleTick225StartPoint.x, mRollScaleTick225StartPoint.y,
                mRollScaleTick225EndPoint.x, mRollScaleTick225EndPoint.y, mRollScalePaint);

        canvas.drawLine(mRollScaleTick240StartPoint.x, mRollScaleTick240StartPoint.y,
                mRollScaleTick240EndPoint.x, mRollScaleTick240EndPoint.y, mRollScalePaint);

        canvas.drawLine(mRollScaleTick245StartPoint.x, mRollScaleTick245StartPoint.y,
                mRollScaleTick245EndPoint.x, mRollScaleTick245EndPoint.y, mRollScalePaint);
        canvas.drawLine(mRollScaleTick250StartPoint.x, mRollScaleTick250StartPoint.y,
                mRollScaleTick250EndPoint.x, mRollScaleTick250EndPoint.y, mRollScalePaint);

        canvas.drawLine(mRollScaleTick255StartPoint.x, mRollScaleTick255StartPoint.y,
                mRollScaleTick255EndPoint.x, mRollScaleTick255EndPoint.y, mRollScalePaint);

        canvas.drawLine(mRollScaleTick260StartPoint.x, mRollScaleTick260StartPoint.y,
                mRollScaleTick260EndPoint.x, mRollScaleTick260EndPoint.y, mRollScalePaint);
        canvas.drawLine(mRollScaleTick265StartPoint.x, mRollScaleTick265StartPoint.y,
                mRollScaleTick265EndPoint.x, mRollScaleTick265EndPoint.y, mRollScalePaint);

        //on 270 is the triangle

        canvas.drawLine(mRollScaleTick275StartPoint.x, mRollScaleTick275StartPoint.y,
                mRollScaleTick275EndPoint.x, mRollScaleTick275EndPoint.y, mRollScalePaint);
        canvas.drawLine(mRollScaleTick280StartPoint.x, mRollScaleTick280StartPoint.y,
                mRollScaleTick280EndPoint.x, mRollScaleTick280EndPoint.y, mRollScalePaint);

        canvas.drawLine(mRollScaleTick285StartPoint.x, mRollScaleTick285StartPoint.y,
                mRollScaleTick285EndPoint.x, mRollScaleTick285EndPoint.y, mRollScalePaint);

        canvas.drawLine(mRollScaleTick290StartPoint.x, mRollScaleTick290StartPoint.y,
                mRollScaleTick290EndPoint.x, mRollScaleTick290EndPoint.y, mRollScalePaint);
        canvas.drawLine(mRollScaleTick295StartPoint.x, mRollScaleTick295StartPoint.y,
                mRollScaleTick295EndPoint.x, mRollScaleTick295EndPoint.y, mRollScalePaint);

        canvas.drawLine(mRollScaleTick300StartPoint.x, mRollScaleTick300StartPoint.y,
                mRollScaleTick300EndPoint.x, mRollScaleTick300EndPoint.y, mRollScalePaint);

        canvas.drawLine(mRollScaleTick315StartPoint.x, mRollScaleTick315StartPoint.y,
                mRollScaleTick315EndPoint.x, mRollScaleTick315EndPoint.y, mRollScalePaint);

        //fatHorizon
        canvas.drawLine(mMiddle - mRadius * .55f, mHorizon + mPitch,
                mMiddle + mRadius * .55f, mHorizon + mPitch, mFatHorizonPaint);

        //pitch lines
        final float mPitchScaleOffset = mRawPitch % 10;
        final float radLineOffset = mRadius - mRadius * 0.1f;

        //only draw top 3 lines if in the circle
        if (radLineOffset > ((30 - mPitchScaleOffset) * (mHorizonDivisor))) {
            drawLine(.4f, -30, canvas);
        }

        if (radLineOffset > ((25 - mPitchScaleOffset) * (mHorizonDivisor))) {
            drawLine(.2f, -25, canvas);
        }
        if (radLineOffset > ((20 - mPitchScaleOffset) * (mHorizonDivisor))) {
            drawLine(.4f, -20, canvas);
        }

        drawLine(.2f, -15, canvas);
        drawLine(.4f, -10, canvas);
        drawLine(.2f, -5, canvas);
        drawLine(.4f, 0, canvas);
        drawLine(.2f, 5, canvas);
        drawLine(.4f, 10, canvas);
        drawLine(.2f, 15, canvas);

        //only draw the bottom 3 lines if in the circle (which is invisible on the bottom)
        if (radLineOffset > ((20 + mPitchScaleOffset) * (mHorizonDivisor))) {
            drawLine(.4f, 20, canvas);
        }

        if (radLineOffset > ((25 + mPitchScaleOffset) * (mHorizonDivisor))) {
            drawLine(.2f, 25, canvas);
        }

        if (radLineOffset > ((30 + mPitchScaleOffset) * (mHorizonDivisor))) {
            drawLine(.4f, 30, canvas);
        }

        //all dashed lines are drawn here; see this.drawLine() for details
        canvas.drawPath(mDashedPitchPath, mUiStrokedPaint);
        mDashedPitchPath.reset();

        canvas.restore();

        //draw everything that does not move
        canvas.drawPath(mRollScaleBottomTrianglePath, mRollScalePaint);

        canvas.drawPath(mStaticTriangleBrightPath, mStaticHorizonBrightPaint);
        canvas.drawPath(mStaticTriangleDarkPath, mStaticHorizonDarkPaint);

        canvas.drawPath(mStaticLeftBarBrightPath, mStaticHorizonBrightPaint);
        canvas.drawPath(mStaticLeftBarDarkPath, mStaticHorizonDarkPaint);

        canvas.drawPath(mStaticRightBarBrightPath, mStaticHorizonBrightPaint);
        canvas.drawPath(mStaticRightBarDarkPath, mStaticHorizonDarkPaint);

    }

    private void drawLine(float lenFactor, int offset, Canvas canvas) {
        final float pitchScaleOffset = mRawPitch % 10;
        final float fromX = mMiddle - mRadius * lenFactor;
        final float y = mHorizon + ((offset + pitchScaleOffset) * (mHorizonDivisor));
        final float toX = mMiddle + mRadius * lenFactor;

        if (y < mHorizon + mPitch) {
            canvas.drawLine(fromX, y, toX, y, mUiPaint);
        } else {
            //we want a dashed line here, drawLine does not
            //support this with GPU support on some versions. so we'll use a path.
            //We'll put all lines in one path and draw it in onDraw();

            //TODO: maybe better use drawLines() (with "s") and draw a line for each dash. drawLines() is running on the GPU, drawPath on the CPU
            mDashedPitchPath.moveTo(fromX, y);
            mDashedPitchPath.lineTo(toX, y);
        }

        final double d = (Math.abs(Math.round(
                (((offset + pitchScaleOffset) * (mHorizonDivisor)) - mPitch) / mHorizonDivisor)));
        final String label = String.format("%s", (int) d);

        if (label != null && d != 0 && d <= 90 && offset % 10 == 0) {
            mUiPaint.getTextBounds(label, 0, label.length(), textBounds);
            canvas.drawText(label, fromX - textBounds.width() - 4, y - textBounds.exactCenterY(),
                    mUiPaint);
            canvas.drawText(label, toX + 2, y - textBounds.exactCenterY(), mUiPaint);
        }
    }
}

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.Spinner;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ViewAnimator;
import org.librepilot.lp2go.MainActivity;
import org.librepilot.lp2go.R;
import org.librepilot.lp2go.VisualLog;
import org.librepilot.lp2go.helper.CompatHelper;
import org.librepilot.lp2go.helper.H;
import org.librepilot.lp2go.helper.SettingsHelper;
import org.librepilot.lp2go.uavtalk.UAVTalkMissingObjectException;
import org.librepilot.lp2go.uavtalk.UAVTalkObject;
import org.librepilot.lp2go.uavtalk.UAVTalkObjectListener;
import org.librepilot.lp2go.uavtalk.UAVTalkXMLObject;
import org.librepilot.lp2go.ui.SingleToast;
import org.librepilot.lp2go.ui.alertdialog.EnumInputAlertDialog;
import org.librepilot.lp2go.ui.alertdialog.NumberInputAlertDialog;
import org.librepilot.lp2go.ui.pfd.PfdRollPitchView;
import java.text.MessageFormat;

        //reset
        ma.setTxObjects(0);
        ma.setRxObjectsBad(0);
        ma.setRxObjectsGood(0);
    }

    private void updateHealth() {
        final MainActivity ma = getMainActivity();
        txtObjectLogTx.setText(
                H.k(String.valueOf(ma.getTxObjects() * MainActivity.POLL_SECOND_FACTOR)));
        txtObjectLogRxGood.setText(
                H.k(String.valueOf(ma.getRxObjectsGood() * MainActivity.POLL_SECOND_FACTOR)));
        txtObjectLogRxBad.setText(
                H.k(String.valueOf(ma.getRxObjectsBad() * MainActivity.POLL_SECOND_FACTOR)));

        setPacketIcons();

        setTextBGColor(txtAtti, getData("SystemAlarms", "Alarm", "Attitude").toString());
        setTextBGColor(txtStab, getData("SystemAlarms", "Alarm", "Stabilization").toString());
        setTextBGColor(txtPath, getData("PathStatus", "Status").toString());
        setTextBGColor(txtPlan, getData("SystemAlarms", "Alarm", "PathPlan").toString());

        setText(txtGPSSatsInView, getData("GPSSatellites", "SatsInView").toString());
        setTextBGColor(txtGPS, getData("SystemAlarms", "Alarm", "GPS").toString());
        setTextBGColor(txtSensor, getData("SystemAlarms", "Alarm", "Sensors").toString());
        setTextBGColor(txtAirspd, getData("SystemAlarms", "Alarm", "Airspeed").toString());
        setTextBGColor(txtMag, getData("SystemAlarms", "Alarm", "Magnetometer").toString());

        setTextBGColor(txtInput, getData("SystemAlarms", "Alarm", "Receiver").toString());
        setTextBGColor(txtOutput, getData("SystemAlarms", "Alarm", "Actuator").toString());
        setTextBGColor(txtI2C, getData("SystemAlarms", "Alarm", "I2C").toString());
        setTextBGColor(txtTelemetry, getData("SystemAlarms", "Alarm", "Telemetry").toString());

        setText(txtHealthAlertDiaFAlgo, getData("RevoSettings", "FusionAlgorithm").toString());

        setTextBGColor(txtBatt, getData("SystemAlarms", "Alarm", "Battery").toString());
        setTextBGColor(txtTime, getData("SystemAlarms", "Alarm", "FlightTime").toString());
        setTextBGColor(txtConfig, getData("SystemAlarms", "ExtendedAlarmStatus",
                "SystemConfiguration").toString());

        setTextBGColor(txtBoot, getData("SystemAlarms", "Alarm", "BootFault").toString());
        setTextBGColor(txtMem, getData("SystemAlarms", "Alarm", "OutOfMemory").toString());
        setTextBGColor(txtStack, getData("SystemAlarms", "Alarm", "StackOverflow").toString());
        setTextBGColor(txtEvent, getData("SystemAlarms", "Alarm", "EventSystem").toString());
        setTextBGColor(txtCPU, getData("SystemAlarms", "Alarm", "CPUOverload").toString());
    }

    private void updateInfo() {
        setText(txtArmed, getData("FlightStatus", "Armed").toString());

        setText(txtFlightTime, H.getDateFromMs(getData("SystemStats", "FlightTime").toString()));

        setText(txtVolt, getFloatData("FlightBatteryState", "Voltage", 4));
        setText(txtAmpere, getFloatData("FlightBatteryState", "Current", 4));
        setText(txtmAh, getFloatData("FlightBatteryState", "ConsumedEnergy", 3));
        setText(txtTimeLeft, H.getDateFromSeconds(
                getData("FlightBatteryState", "EstimatedFlightTime").toString()));

        setText(txtHealthAlertDiaBatCap, getData("FlightBatterySettings", "Capacity").toString());
        setText(txtHealthAlertDiaBatCls, getData("FlightBatterySettings", "NbCells").toString());

        setText(txtAltitude, getFloatOffsetData("BaroSensor", "Altitude",
                getString(R.string.OFFSET_BAROSENSOR_ALTITUDE, R.string.APP_ID)));

        setText(txtAltitudeAccel, getFloatData("VelocityState", "Down", 2));

        String flightModeSwitchPosition =
                getData("ManualControlCommand", "FlightModeSwitchPosition", true).toString();

        try {   //FlightMode in GCS is 1...n, so add "1" to be user friendly
            setText(txtModeNum,
                    String.valueOf(Integer.parseInt(flightModeSwitchPosition) + 1));
        } catch (NumberFormatException e) {
            VisualLog.e(MessageFormat.format("Could not parse numeric Flightmode: {0}",
                    flightModeSwitchPosition));
        }

        setText(txtModeFlightMode, getData("FlightStatus", "FlightMode", true).toString());
        setText(txtModeAssistedControl, getData("FlightStatus", "FlightModeAssist", true)
                .toString());
    }

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

        final MainActivity ma = getMainActivity();

        if (isView(R.id.root_main_inc_health)) {
            updateHealth();
        }

        if (isView(R.id.root_main_inc_info)) {
            updateInfo();
        }

        if (isView(R.id.root_main_inc_map)) {
            getMainActivity().mVcList.get(ViewController.VIEW_MAP).update();
        }

        if (isView(R.id.root_main_inc_pfd) &&
                ma.mFcDevice.getObjectTree().getListener("AttitudeState") == null) {
            try {
                try {
                    getMainActivity().mFcDevice.getObjectTree().setListener("AttitudeState", this);
                } catch (IllegalStateException e1) {
                    getMainActivity().mFcDevice.getObjectTree().removeListener("AttitudeState");
                    getMainActivity().mFcDevice.getObjectTree().setListener("AttitudeState", this);
                }
            } catch (NullPointerException ignored) {
            }
        }

        getMainActivity().mFcDevice.requestObject("FlightBatteryState");
        getMainActivity().mFcDevice.requestObject("SystemStats");
    }

    private void resetHealth() {
        final Context c = getMainActivity().getApplicationContext();


        CompatHelper.setBackground(txtAtti, c, R.drawable.rounded_corner_unini);
