de.tum.frm2.nicos_android.gui.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for de.tum.frm2.nicos_android.gui.MainActivity.java

Source

//
// Copyright (C) 2016 Andreas Schulz <andreas.schulz@frm2.tum.de>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 US

package de.tum.frm2.nicos_android.gui;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.preference.PreferenceManager;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ViewSwitcher;

import com.sothree.slidinguppanel.SlidingUpPanelLayout;

import net.razorvine.pickle.objects.ClassDict;
import net.razorvine.pickle.objects.ClassDictConstructor;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;

import de.tum.frm2.nicos_android.nicos.ConnectionData;
import de.tum.frm2.nicos_android.nicos.Device;
import de.tum.frm2.nicos_android.nicos.DeviceStatus;
import de.tum.frm2.nicos_android.nicos.NicosMessageLevel;
import de.tum.frm2.nicos_android.nicos.NicosStatus;
import de.tum.frm2.nicos_android.util.NicosCallbackHandler;
import de.tum.frm2.nicos_android.nicos.NicosClient;
import de.tum.frm2.nicos_android.R;

public class MainActivity extends AppCompatActivity implements NicosCallbackHandler {
    public final static String MESSAGE_DAEMON_INFO = "de.tum.frm2.nicos_android.MESSAGE_DAEMON_INFO";
    private ArrayList<Device> _moveables;
    private DeviceViewAdapter _devicesAdapter;
    private Handler _uiThread;
    private boolean _visible;
    private boolean _canAccessDevices;
    private int _current_status;
    private String _uniquePrefix;
    private SlidingUpPanelLayout _slidingUpPanelLayout;
    private TextView _currentDeviceTextView;
    private TextView _currentDeviceValueTextView;
    private ImageView _currentDeviceStatusImageView;
    private Button _coarseStepLeftButton;
    private Button _fineStepLeftButton;
    private Button _stopButton;
    private Button _fineStepRightButton;
    private Button _coarseStepRightButton;
    private EditText _coarseStepEditText;
    private EditText _fineStepEditText;
    private Device _currentDevice;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        // Set up the container and its adapter for devices.
        _moveables = new ArrayList<>();
        View content_main = findViewById(R.id.content_main);
        _devicesAdapter = new DeviceViewAdapter(MainActivity.this, _moveables);
        final ListView deviceListView = (ListView) content_main.findViewById(R.id.deviceListView);
        deviceListView.setAdapter(_devicesAdapter);

        // Set up _uiThread to be a handler that runs runnables on the UI thread.
        // advantage over runOnUiThread() is that we can actually control the state of the thread
        // and cancel it, if needed.
        _uiThread = new Handler(Looper.getMainLooper());

        // Default boolean values: Activity is visible, devices not yet fetched
        _visible = true;
        _canAccessDevices = false;

        ConnectionData connData = (ConnectionData) getIntent()
                .getSerializableExtra(LoginActivity.MESSAGE_CONNECTION_DATA);
        _uniquePrefix = connData.getHost() + connData.getUser();

        // References to the 'steps' views.
        _coarseStepEditText = (EditText) findViewById(R.id.coarseStepEditText);
        _fineStepEditText = (EditText) findViewById(R.id.fineStepEditText);

        TextView.OnEditorActionListener onEditorActionListener = new TextView.OnEditorActionListener() {
            @Override
            public boolean onEditorAction(TextView textView, int actionID, KeyEvent keyEvent) {
                if (actionID == EditorInfo.IME_ACTION_DONE) {
                    _fineStepEditText.clearFocus();
                    // Hide keyboard.
                    InputMethodManager manager = (InputMethodManager) getSystemService(
                            Context.INPUT_METHOD_SERVICE);
                    manager.hideSoftInputFromWindow(textView.getWindowToken(), 0);
                }
                return false;
            }
        };

        // When hitting 'enter' or 'ok' on the keyboard while in EditText, apply changes and hide
        // keyboard.
        _coarseStepEditText.setOnEditorActionListener(onEditorActionListener);
        _fineStepEditText.setOnEditorActionListener(onEditorActionListener);

        TextWatcher textWatcher = new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
            }

            @Override
            public void afterTextChanged(Editable editable) {
                saveSteps();
            }
        };

        _coarseStepEditText.addTextChangedListener(textWatcher);
        _fineStepEditText.addTextChangedListener(textWatcher);

        // Reference to the bottom slider panel + initial height.
        _slidingUpPanelLayout = (SlidingUpPanelLayout) findViewById(R.id.sliding_layout);

        // Change behavior of Panel when state changes.
        _slidingUpPanelLayout.addPanelSlideListener(new SlidingUpPanelLayout.PanelSlideListener() {
            @Override
            public void onPanelSlide(View panel, float slideOffset) {
            }

            @Override
            public void onPanelStateChanged(View panel, SlidingUpPanelLayout.PanelState previousState,
                    SlidingUpPanelLayout.PanelState newState) {
                if (newState == SlidingUpPanelLayout.PanelState.COLLAPSED) {
                    // When hiding the panel, also hide the keyboard and clear all focuses.
                    InputMethodManager manager = (InputMethodManager) getSystemService(
                            Context.INPUT_METHOD_SERVICE);
                    manager.hideSoftInputFromWindow(panel.getWindowToken(), 0);
                    _coarseStepEditText.clearFocus();
                    _coarseStepEditText.setImeOptions(EditorInfo.IME_ACTION_DONE);
                    _fineStepEditText.clearFocus();
                    _fineStepEditText.setImeOptions(EditorInfo.IME_ACTION_DONE);
                }

            }
        });

        // Reference to current device and the 3 subviews of currentDeviceView.
        // That means the name label, value label and status image.
        _currentDevice = null;
        _currentDeviceTextView = (TextView) findViewById(R.id.deviceNameTextView);
        _currentDeviceValueTextView = (TextView) findViewById(R.id.deviceValueTextView);
        _currentDeviceStatusImageView = (ImageView) findViewById(R.id.statusledView);

        // References to the 5 control buttons.
        _coarseStepLeftButton = (Button) findViewById(R.id.coarseStepLeftButton);
        _coarseStepLeftButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                onStepButtonClicked(_coarseStepLeftButton);
            }
        });
        _fineStepLeftButton = (Button) findViewById(R.id.fineStepLeftButton);
        _fineStepLeftButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                onStepButtonClicked(_fineStepLeftButton);
            }
        });
        _stopButton = (Button) findViewById(R.id.stopButton);
        _stopButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                onStopButtonClicked();
            }
        });
        _fineStepRightButton = (Button) findViewById(R.id.fineStepRightButton);
        _fineStepRightButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                onStepButtonClicked(_fineStepRightButton);
            }
        });
        _coarseStepRightButton = (Button) findViewById(R.id.coarseStepRightButton);
        _coarseStepRightButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                onStepButtonClicked(_coarseStepRightButton);
            }
        });
        _slidingUpPanelLayout.setEnabled(false);

        // Change behavior when clicking/tapping on a device.
        deviceListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
                Device device = (Device) deviceListView.getItemAtPosition(position);
                onDeviceSelected(device);
            }
        });

        NicosClient.getClient().registerCallbackHandler(this);
        new Thread(new Runnable() {
            @Override
            public void run() {
                on_client_connected();
            }
        }).start();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        if (id == R.id.action_settings) {
            Intent intent = new Intent(this, SettingsActivity.class);
            startActivity(intent);
            return true;
        }

        if (id == R.id.action_connection_info) {
            Intent intent = new Intent(this, ConnectionInfoActivity.class);
            intent.putExtra(MESSAGE_DAEMON_INFO, NicosClient.getClient().getNicosBanner());
            startActivity(intent);
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onBackPressed() {
        if (_slidingUpPanelLayout.getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED) {
            _slidingUpPanelLayout.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
            return;
        }
        _visible = false;
        NicosClient.getClient().unregisterCallbackHandler(this);
        new Thread(new Runnable() {
            @Override
            public void run() {
                NicosClient.getClient().disconnect();
            }
        }).start();
        // null parameter -> remove ALL runnables.
        _uiThread.removeCallbacks(null);
        super.onBackPressed();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        _visible = false;
        NicosClient.getClient().unregisterCallbackHandler(this);
    }

    @Override
    protected void onResume() {
        super.onResume();
        _visible = true;

        // Initialize design for buttons.
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
        boolean useJK = prefs.getBoolean(getResources().getString(R.string.key_jk_design_switch), false);
        ViewSwitcher switcher = (ViewSwitcher) findViewById(R.id.viewSwitch);
        if (useJK) {
            if (switcher.getCurrentView() == findViewById(R.id.layoutSmallButtons)) {
                switcher.showNext();
                setPanelHeight(false);
            }
        } else {
            if (switcher.getCurrentView() == findViewById(R.id.layoutBigButtons)) {
                switcher.showNext();
                setPanelHeight(true);
            }
        }
    }

    private void setPanelHeight(boolean withButtonsVisible) {
        int panelHeight = 0;
        RelativeLayout currentDeviceLayout = (RelativeLayout) findViewById(R.id.currentDeviceView);
        currentDeviceLayout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
        panelHeight += currentDeviceLayout.getMeasuredHeight();
        if (withButtonsVisible) {
            RelativeLayout smallButtonsLayout = (RelativeLayout) findViewById(R.id.layoutSmallButtons);
            smallButtonsLayout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
            panelHeight += smallButtonsLayout.getMeasuredHeight();
        }
        _slidingUpPanelLayout.setPanelHeight(panelHeight);
    }

    @Override
    protected void onSaveInstanceState(Bundle instance) {
        if (_currentDevice != null) {
            instance.putString("currentDevice", _currentDevice.getName());
        }
        super.onSaveInstanceState(instance);
    }

    @Override
    protected void onRestoreInstanceState(Bundle instance) {
        final String previousDeviceName = instance.getString("currentDevice");
        if (previousDeviceName != null) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (!_canAccessDevices) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            return;
                        }
                    }
                    _uiThread.post(new Runnable() {
                        @Override
                        public void run() {
                            onDeviceSelected(getDeviceByCacheName(previousDeviceName.toLowerCase()));
                        }
                    });
                }
            }).start();
        }
        super.onRestoreInstanceState(instance);
    }

    private void saveSteps() {
        // Try saving the steps, if they are valid. Else, just ignore saving.
        SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit();
        try {
            double coarse = Double.parseDouble(_coarseStepEditText.getText().toString());
            double fine = Double.parseDouble(_fineStepEditText.getText().toString());
            String coarseKey = _uniquePrefix + _currentDevice.getName() + "coarse";
            String fineKey = _uniquePrefix + _currentDevice.getName() + "fine";
            editor.putLong(coarseKey, Double.doubleToRawLongBits(coarse));
            editor.putLong(fineKey, Double.doubleToRawLongBits(fine));
            editor.apply();
        } catch (Exception e) {
            // Probably invalid steps
        }
    }

    @Override
    public void handleSignal(String signal, Object data, Object args) {
        switch (signal) {
        case "broken":
            final String error = (String) data;
            // Connection is broken. Try to disconnect what's left and go back to login screen.
            NicosClient.getClient().unregisterCallbackHandler(this);
            NicosClient.getClient().disconnect();
            if (!_visible)
                return;

            // Activity is still visible, user probably didn't intend to shut down connection.
            // We display an error.
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    AlertDialog alertDialog;
                    try {
                        alertDialog = new AlertDialog.Builder(MainActivity.this).create();
                        alertDialog.setTitle("Disconnected");
                        alertDialog.setMessage(error);
                        alertDialog.setButton(AlertDialog.BUTTON_NEGATIVE, "Okay",
                                new DialogInterface.OnClickListener() {
                                    public void onClick(DialogInterface dialog, int which) {
                                        dialog.dismiss();
                                        finish();
                                    }
                                });
                        alertDialog.setCancelable(false);
                        alertDialog.setCanceledOnTouchOutside(false);
                        alertDialog.show();
                    } catch (Exception e) {
                        try {
                            finish();
                        } catch (Exception e2) {
                            // User probably quit the application.
                        }
                        // Activity isn't running anymore
                        // (user probably put application in background).
                    }
                }
            });
            break;

        case "cache":
            on_client_cache((Object[]) data);
            break;

        case "status":
            // data = tuple of (status, linenumber)
            _current_status = (int) ((Object[]) data)[0];
            break;

        case "message":
            final ArrayList msgList = (ArrayList) data;
            if ((int) msgList.get(2) == NicosMessageLevel.ERROR) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(getApplicationContext(),
                                NicosMessageLevel.level2name(NicosMessageLevel.ERROR) + ": " + msgList.get(3),
                                Toast.LENGTH_SHORT).show();
                    }
                });
            }
            break;

        case "error":
            final String msg = (String) data;
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
                }
            });
            break;
        }
    }

    private void onStopButtonClicked() {
        exec_command("stop(" + _currentDevice.getName() + ")");
    }

    private void onStepButtonClicked(Button btn) {
        // The EditText which needs to be parsed for the correct step.
        EditText editTextToBeParsed = null;
        // Whether to multiply by -1 (for right or left steps)
        short factor = 0;

        if (btn == _coarseStepLeftButton) {
            editTextToBeParsed = _coarseStepEditText;
            factor = -1;
        }

        else if (btn == _fineStepLeftButton) {
            editTextToBeParsed = _fineStepEditText;
            factor = -1;
        }

        else if (btn == _coarseStepRightButton) {
            editTextToBeParsed = _coarseStepEditText;
            factor = 1;
        }

        else if (btn == _fineStepRightButton) {
            editTextToBeParsed = _fineStepEditText;
            factor = 1;
        }

        Double step = null;
        try {
            if (editTextToBeParsed != null) {
                Editable doubleString = editTextToBeParsed.getText();
                if (doubleString != null) {
                    step = Double.parseDouble(doubleString.toString());
                }
            }
        } catch (Exception e) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(getApplicationContext(), "Invalid step.", Toast.LENGTH_SHORT).show();
                }
            });
            return;
        }

        if (step != null && _currentDevice.getValue() != null) {
            Double current;
            try {
                current = (double) _currentDevice.getValue();
            } catch (ClassCastException e) {
                try {
                    current = Double.parseDouble(_currentDevice.getValue().toString());
                } catch (NumberFormatException e1) {
                    return;
                }
            }
            double newVal = current + step * factor;
            exec_command("move(" + _currentDevice.getName() + ", " + String.valueOf(newVal) + ")");
        }
    }

    private void onDeviceSelected(Device device) {
        Object limits = device.getParam("userlimits");
        Object mapping = device.getParam("mapping");
        if (limits == null && mapping == null) {
            Toast.makeText(getApplicationContext(), "Cannot move " + device.getName() + ": Limits unknown",
                    Toast.LENGTH_SHORT).show();
            return;
        }

        _currentDevice = device;
        _currentDeviceTextView.setText(device.getName());
        _currentDeviceValueTextView.setText(device.getFormattedValue());
        _currentDeviceStatusImageView.setImageResource(DeviceStatus.getStatusResource(device.getStatus()));

        if (limits != null) {
            Object o_max = ((Object[]) limits)[1];
            double max = (double) o_max;

            // Try to read a value from the preferences.
            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
            String coarseKey = _uniquePrefix + _currentDevice.getName() + "coarse";
            String fineKey = _uniquePrefix + _currentDevice.getName() + "fine";
            if (prefs.contains(coarseKey) && prefs.contains(fineKey)) {
                long dec_coarse = prefs.getLong(coarseKey, 0);
                long dec_fine = prefs.getLong(fineKey, 0);
                _coarseStepEditText.setText(String.valueOf(Double.longBitsToDouble(dec_coarse)));
                _fineStepEditText.setText(String.valueOf(Double.longBitsToDouble(dec_fine)));
            } else {
                // infer default steps from the max limit.
                _coarseStepEditText.setText(String.valueOf(max / 5));
                _fineStepEditText.setText(String.valueOf(max / 10));
            }
        } else {
            // TODO: Implement devices with mapping!
            return;
        }

        _coarseStepLeftButton.setEnabled(true);
        _fineStepLeftButton.setEnabled(true);
        _stopButton.setEnabled(true);
        _fineStepRightButton.setEnabled(true);
        _coarseStepRightButton.setEnabled(true);
        _slidingUpPanelLayout.setEnabled(true);
    }

    private void on_client_connected() {
        // Query moveables.
        final ArrayList lowercaseMoveables = (ArrayList) NicosClient.getClient()
                .getDeviceList("nicos.core.device.Moveable", true, null, null);

        // Ask for current daemon status.
        Object untyped_state = NicosClient.getClient().ask("getstatus", null);
        if (untyped_state == null) {
            return;
        }
        final HashMap state = (HashMap) untyped_state;
        Object[] statusTuple = (Object[]) state.get("status");
        _current_status = (int) statusTuple[0];

        // Fill _moveables.
        Runnable uiAddMoveables = new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    // Filter device list for moveables.
                    ArrayList devlist = (ArrayList) state.get("devices");
                    for (Object deviceObject : devlist) {
                        String device = (String) deviceObject;
                        String cachekey = device.toLowerCase();
                        if (!lowercaseMoveables.contains(cachekey)) {
                            continue;
                        }
                        // Create device and add it.
                        Device moveable = new Device(device, cachekey);
                        _moveables.add(moveable);

                    }

                    // Sort devices in place.
                    Collections.sort(_moveables, new Comparator<Device>() {
                        @Override
                        public int compare(Device lhs, Device rhs) {
                            return lhs.getCacheName().compareTo(rhs.getCacheName());
                        }
                    });
                    notify();
                }
            }
        };

        //noinspection SynchronizationOnLocalVariableOrMethodParameter
        synchronized (uiAddMoveables) {
            _uiThread.post(uiAddMoveables);
            try {
                uiAddMoveables.wait();
            } catch (InterruptedException e) {
                return;
            }
        }

        // Query parameters.
        for (final Device device : _moveables) {
            final ArrayList params = NicosClient.getClient().getDeviceParams(device.getCacheName());
            if (params == null) {
                continue;
            }

            // Add params to device.
            Runnable uiAddParams = new Runnable() {
                @Override
                public void run() {
                    synchronized (this) {
                        for (Object param : params) {
                            Object[] tuple = (Object[]) param;
                            // Split device name from parameter name.
                            // e.g. t/fmtstr -> fmtstr
                            String[] keyParts = ((String) tuple[0]).split(("/"));
                            device.addParam(keyParts[1], tuple[1]);
                        }
                        notify();
                    }
                }
            };

            //noinspection SynchronizationOnLocalVariableOrMethodParameter
            synchronized (uiAddParams) {
                _uiThread.post(uiAddParams);
                try {
                    uiAddParams.wait();
                } catch (InterruptedException e) {
                    return;
                }
            }
        }

        // Remove all moveables without abslimits.
        Runnable uiRemoveDevicesWithoutLimits = new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    Iterator<Device> it = _moveables.iterator();
                    while (it.hasNext()) {
                        Device device = it.next();
                        if (device.getParam("abslimits") == null) {
                            it.remove();
                        }
                    }
                    _devicesAdapter.notifyDataSetChanged();
                    _canAccessDevices = true;
                    notify();
                }
            }
        };

        //noinspection SynchronizationOnLocalVariableOrMethodParameter
        synchronized (uiRemoveDevicesWithoutLimits) {
            _uiThread.post(uiRemoveDevicesWithoutLimits);
            try {
                uiRemoveDevicesWithoutLimits.wait();
            } catch (InterruptedException e) {
                return;
            }
        }

        // Query statuses and values of all devices.
        for (final Device device : _moveables) {
            // Query this device's status.
            Object untypedStatus = NicosClient.getClient().getDeviceStatus(device.getName());
            Object[] tupleStatus;

            try {
                tupleStatus = (Object[]) untypedStatus;
                if (tupleStatus == null) {
                    throw new RuntimeException();
                }
            } catch (Exception e) {
                tupleStatus = new Object[] { -1, null };
            }

            final int status = (int) tupleStatus[0];
            final Object value = NicosClient.getClient().getDeviceValue(device.getName());

            // Query value types.
            Object valuetype = NicosClient.getClient().getDeviceValuetype(device.getName());
            String pyclass;
            final Class valueclass;

            if (valuetype == null) {
                // Response timed out
                continue;
            }
            if (valuetype.getClass() == ClassDictConstructor.class) {
                // Java .o
                Object[] o = {};
                ClassDict s = ((ClassDict) ((ClassDictConstructor) valuetype).construct(o));
                s.__setstate__(new HashMap<String, Object>());
                pyclass = (String) s.get("__class__");
            } else {
                pyclass = (String) ((ClassDict) valuetype).get("__class__");
            }

            switch (pyclass) {
            case "__builtin__.float":
                valueclass = Double.class;
                break;
            case "__builtin__.double":
                valueclass = Double.class;
                break;
            case "nicos.core.params.tupleof":
                valueclass = Object[].class;
                break;
            case "nicos.core.params.oneof":
                valueclass = String.class;
                break;
            case "nicos.core.params.oneofdict":
                valueclass = String.class;
                break;
            case "nicos.core.params.limits":
                valueclass = Object[].class;
                break;
            default:
                valueclass = String.class;
                break;
            }

            // A runnable to update the device in UI thread with new status + value.
            Runnable uiChangeValue = new Runnable() {
                @Override
                public void run() {
                    synchronized (this) {
                        device.setStatus(status);
                        device.setValue(value);
                        device.setValuetype(valueclass);
                        _devicesAdapter.notifyDataSetChanged();
                        if (_currentDevice == device) {
                            _currentDeviceValueTextView.setText(device.getFormattedValue());
                        }
                        notify();
                    }
                }
            };

            //noinspection SynchronizationOnLocalVariableOrMethodParameter
            synchronized (uiChangeValue) {
                _uiThread.post(uiChangeValue);
                try {
                    uiChangeValue.wait();
                } catch (InterruptedException e) {
                    return;
                }
            }
        }
    }

    private Device getDeviceByCacheName(String cacheName) {
        for (Device d : _moveables) {
            if (d.getCacheName().equals(cacheName)) {
                return d;
            }
        }
        return null;
    }

    private void exec_command(final String command) {
        if (!(_current_status == NicosStatus.STATUS_IDLE || _current_status == NicosStatus.STATUS_IDLEEXC)) {
            // Server is not idle.
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            switch (which) {
                            case DialogInterface.BUTTON_NEUTRAL:
                                new Thread(new Runnable() {
                                    @Override
                                    public void run() {
                                        NicosClient.getClient().tell("exec", command);
                                    }
                                }).start();
                                break;
                            case DialogInterface.BUTTON_NEGATIVE:
                                return;
                            case DialogInterface.BUTTON_POSITIVE:
                                new Thread(new Runnable() {
                                    @Override
                                    public void run() {
                                        NicosClient.getClient().run(command);
                                    }
                                }).start();
                                break;
                            }
                        }
                    };

                    AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
                    builder.setMessage("A script is currently running. What do you want to do?");
                    builder.setNeutralButton("Execute now!", dialogClickListener);
                    builder.setNegativeButton("Cancel", dialogClickListener);
                    builder.setPositiveButton("Queue script", dialogClickListener);
                    int version = Build.VERSION.SDK_INT;
                    int color;
                    if (version >= 23) {
                        color = ContextCompat.getColor(MainActivity.this, R.color.colorPrimary);
                    } else {
                        // It's only deprecated since API level 23.
                        //noinspection deprecation
                        color = getResources().getColor(R.color.colorPrimary);
                    }
                    AlertDialog dlg = builder.create();
                    dlg.show();
                    dlg.getButton(AlertDialog.BUTTON_NEUTRAL).setTextColor(color);
                    dlg.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(Color.RED);
                    dlg.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(color);
                }
            });
        }

        else {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    NicosClient.getClient().run(command);
                }
            }).start();
        }
    }

    private void on_client_cache(Object[] data) {
        if (!_canAccessDevices) {
            return;
        }
        String key = (String) data[1];
        String[] splitted = key.split("/");
        String devname = splitted[0];
        String subkey = splitted[1];

        Device maybeDevice;
        maybeDevice = getDeviceByCacheName(devname);
        if (maybeDevice == null) {
            // A device not in the list, probably not a moveable.
            return;
        }

        final Device curdev = maybeDevice;
        final Object value = data[3];

        switch (subkey) {
        case "status":
            // Cache string.
            String tuple = (String) value;
            // cut '(' and ')'
            tuple = tuple.substring(1, tuple.length() - 1);
            String[] tupelupel = tuple.split(",");
            final int status = Integer.valueOf(tupelupel[0]);
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    curdev.setStatus(status);
                    if (_currentDevice == curdev) {
                        _currentDeviceStatusImageView.setImageResource(DeviceStatus.getStatusResource(status));
                    }
                    _devicesAdapter.notifyDataSetChanged();
                }
            });
            break;

        case "value":
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    curdev.setValueFromCache(value.toString());
                    if (_currentDevice == curdev) {
                        _currentDeviceValueTextView.setText(curdev.getFormattedValue());
                    }
                    _devicesAdapter.notifyDataSetChanged();
                }
            });
            break;

        case "fmtstr":
            final String fmt = (String) data[3];
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    curdev.addParam("fmtstr", fmt);
                    if (_currentDevice == curdev) {
                        _currentDeviceValueTextView.setText(curdev.getFormattedValue());
                    }
                    _devicesAdapter.notifyDataSetChanged();
                }
            });
            break;
        }
    }
}