package com.pascal.lcltest;

import android.app.*;
import android.content.*;
import android.os.*;
import android.widget.*;
import android.util.*;
import android.graphics.*;
import android.text.*;
import android.view.*;
import android.view.inputmethod.*;
import android.view.MenuItem.*;
import android.content.res.Configuration;
import android.content.Intent;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.telephony.SmsManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import java.util.*;

public class LCLActivity extends Activity implements SensorEventListener, LocationListener
{
  // -------------------------------------------
  // Input connection to get character events
  // -------------------------------------------
  private class LCLInputConnection extends BaseInputConnection
  {
    private SpannableStringBuilder _editable;
    View _lclView;

    public LCLInputConnection(View targetView, boolean fullEditor)
    {
      super(targetView, fullEditor);
      _lclView = (View) targetView;
    }

/*    public Editable getEditable()
    {
      if (_editable == null)
      {
        _editable = (SpannableStringBuilder) Editable.Factory.getInstance()
        .newEditable("Placeholder");
      }
      return _editable;
    } This crashes in HTC!!! */

    // This method sends a text to be added at the current cursor position
    @Override public boolean commitText(CharSequence text, int newCursorPosition)
    {
      //if (_editable != null) _editable.append(text);
      Log.v("lclproject", "LCLInputConnection.commitText =" + text + " newCursorPosition=" + Integer.toString(newCursorPosition));

      // Send each character of the string
      int eventResult, i;
      for (i = 0; i<text.length(); i++)
      {
        eventResult = LCLOnKey(-1, 0, null, (int) text.charAt(i));
        ProcessEventResult(eventResult);
      }
      return true;
    }

    @Override public boolean deleteSurroundingText(int leftLength, int rightLength)
    {
      Log.v("lclproject", "LCLInputConnection.deleteSurroundingText left=" + Integer.toString(leftLength) + " right=" + Integer.toString(rightLength));

      // For each left surrounding text deletion, send a backspace key
      int eventResult, i;
      for (i = 0; i<leftLength; i++)
      {
        eventResult = LCLOnKey(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL, null, (char) 0);
        ProcessEventResult(eventResult);
        eventResult = LCLOnKey(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL, null, (char) 0);
        ProcessEventResult(eventResult);
      }

      // For each right surrounding text deletion, send a del key
      // KEYCODE_FORWARD_DEL Since: API Level 11
      /*for (i = 0; i<leftLength; i++)
      {
        eventResult = LCLOnKey(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_FORWARD_DEL, null, (char) 0);
        if ((eventResult & 1) != 0) lclsurface.postInvalidate();
        eventResult = LCLOnKey(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_FORWARD_DEL, null, (char) 0);
        if ((eventResult & 1) != 0) lclsurface.postInvalidate();
      }*/

      return super.deleteSurroundingText(leftLength, rightLength);
    }
  }

  // -------------------------------------------
  // Our drawing surface
  // -------------------------------------------
  private class LCLSurface extends View
  {
    private Bitmap canvasbitmap; // This is the buffered canvas bitmap, which is reused until the canvas size changes

    public LCLSurface(Context context)
    {
      super(context);
      // Allows View.postInvalidate() to work
      setWillNotDraw(false);
      // We already double buffer, so no need for a second one
      setWillNotCacheDrawing(true);
      // Set focus on us to get keyboard events
      requestFocus();
      setFocusableInTouchMode(true);
    }

    @Override protected void onDraw(Canvas canvas)
    {
      //Log.i("lclapp", "onDraw started");
      int lWidth = getWidth();
      int lHeight = getHeight();
      int oldlclformwidth = lclformwidth;

      lclformwidth = lWidth;
      lclformheight = lHeight;
      lclscreenwidth = lclformwidth;
      lclscreenheight = lclformheight;

      // Check if we rotated in the draw event.
      // LCLOnConfigurationChanged is called from here because the Android event
      // OnConfigurationChanged can't return the new form width =(
      // see http://stackoverflow.com/questions/2524683/how-to-get-new-width-height-of-root-layout-in-onconfigurationchanged
      if (lWidth != oldlclformwidth) LCLOnConfigurationChanged(lclxdpi, lWidth); // we send xdpi because thats what the LCL uses for Screen.PixelsPerInch

      //Log.v("lclproject", "LCLSurface.onDraw width=" + Integer.toString(lWidth)
      //  + " height=" + Integer.toString(lHeight));

      if ((canvasbitmap == null) || (canvasbitmap.getWidth() != lWidth) || (canvasbitmap.getHeight() != lHeight))
	canvasbitmap = Bitmap.createBitmap(lWidth, lHeight, Bitmap.Config.ARGB_8888);
      LCLDrawToBitmap(lWidth, lHeight, canvasbitmap);
      canvas.drawBitmap(canvasbitmap, 0, 0, null);
      //Log.i("lclapp", "onDraw finished");
    }

    @Override public boolean onKeyDown (int keyCode, KeyEvent event)
    {
      //Log.v("lclproject", "LCLSurface.onKeyDown");
      super.onKeyDown(keyCode, event);
      int eventResult = LCLOnKey(KeyEvent.ACTION_DOWN, keyCode, event, (char) 0);
      ProcessEventResult(eventResult);
      return true;
    }

    @Override public boolean onKeyUp (int keyCode, KeyEvent event)
    {
      // First handle the KeyUp event
      int eventResult = LCLOnKey(KeyEvent.ACTION_UP, keyCode, event, event.getUnicodeChar());
      ProcessEventResult(eventResult);

      // Handling of the Back hardware key
      super.onKeyUp(keyCode, event);
      if ((eventResult & 2) != 0)
      {
        //Log.v("lclproject", "BackKey going to home");
        finish();
        return false; // From the docs it seams that only returning false should do it, but calling finish() is really necessary
      }
      else
      {
        //Log.v("lclproject", "BackKey not going to home");
      }

      // Handling of the Menu hardware key
      if (keyCode == KeyEvent.KEYCODE_MENU)
      {
        flagIsMenuOpen = false; // hard-coding now has a good result, we might change in the future if we ever start getting key events even while the menu is open
        if (flagIsMenuOpen) closeOptionsMenu();
        else openOptionsMenu();
        flagIsMenuOpen = !flagIsMenuOpen;
      }

      return true;
    }

    @Override public boolean onTouchEvent (MotionEvent event)
    {
      int eventResult = LCLOnTouch(event.getX(), event.getY(), event.getAction());
      if ((eventResult | 1) != 0) postInvalidate();
      return true;
    }

    @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs)
    {
      outAttrs.actionLabel = null;
      outAttrs.label = "Test text";
      outAttrs.inputType = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
      outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
      return new LCLInputConnection(this, true);
    }

    @Override public boolean onCheckIsTextEditor()
    {
      return true;
    }
  }

  // Global objects
  LCLSurface lclsurface;
  SensorManager localSensorManager;

  // Utility routines
  public static double[] convertFloatsToDoubles(float[] input)
  {
    if (input == null) return null;
    double[] output = new double[input.length];
    for (int i = 0; i < input.length; i++)
    {  output[i] = input[i]; }
    return output;
  }

  public void ProcessEventResult(int eventResult)
  {
    if (((eventResult & 1) != 0) && (lclsurface != null)) lclsurface.postInvalidate();
    //if ((eventResult & 2) != 0) reserved for BACK key handling and handled in onKeyUp, don't handle here!
  }

  // -------------------------------------------
  // Activity Events
  // -------------------------------------------

  /** Called when the activity is first created. */
  @Override public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
          
    lclsurface = new LCLSurface(this);
    setContentView(lclsurface);
    lclsurface.postInvalidate();

    // Tell the LCL that an OnCreate has happened and what is our instance
    lclformwidth = lclsurface.getWidth();
    lclformheight = lclsurface.getHeight();
    lclscreenwidth = lclformwidth;
    lclscreenheight = lclformheight;
    DisplayMetrics metrics = new DisplayMetrics();
    getWindowManager().getDefaultDisplay().getMetrics(metrics);
    lclxdpi = (int) metrics.xdpi;
    lclydpi = (int) metrics.ydpi;
    LCLOnCreate(this);
  }

  @Override public void onConfigurationChanged (Configuration newConfig)
  {
    super.onConfigurationChanged(newConfig);

    lclformwidth = lclsurface.getWidth();
    lclformheight = lclsurface.getHeight();
    lclscreenwidth = lclformwidth;
    lclscreenheight = lclformheight;
    DisplayMetrics metrics = new DisplayMetrics();
    getWindowManager().getDefaultDisplay().getMetrics(metrics);
    lclxdpi = (int) metrics.xdpi;
    lclydpi = (int) metrics.ydpi;
    // Don't call LCLOnConfigurationChanged, wait for a onDraw instead
    //lclsurface.postInvalidate();
    //Log.i("lclapp", "onConfigurationChanged finished");
  }

  @Override public boolean onCreateOptionsMenu(Menu menu)
  {
    Log.i("lclapp", "onCreateOptionsMenu");
    return super.onCreateOptionsMenu(menu);
  }

  @Override public boolean onPrepareOptionsMenu (Menu menu)
  {
    Log.i("lclapp", "onPrepareOptionsMenu");

    super.onPrepareOptionsMenu(menu);

    int i;

    // First clear the captions list
    for (i = 0; i < lclmenu_captions.length; i++)
      lclmenu_captions[i] = "";

    // Now ask the LCL to fill it
    LCLOnMenuAction(0, 0);

    // And fill the menus with it
    menu.clear();
    for (i = 0; i< lclmenu_captions.length; i++)
    {
      if (lclmenu_captions[i] != "")
      {
        Log.i("lclapp", "onPrepareOptionsMenu item=" + lclmenu_captions[i]);
        MenuItem lMenuItem = menu.add(0, i, 0, lclmenu_captions[i]);
        lMenuItem.setOnMenuItemClickListener(new OnMenuItemClickListener()
        {
          public boolean onMenuItemClick(MenuItem item)
          {
            flagIsMenuOpen = false;
            LCLOnMenuAction(1, item.getItemId());
            return true;
          }
        });
      };
    };

    return true;
  }

  // -------------------------------------------
  // JNI table of Pascal functions
  // -------------------------------------------
  public native int LCLDrawToBitmap(int width, int height, Bitmap bitmap);
  public native int LCLOnTouch(float x, float y, int action);
  public native int LCLOnCreate(Activity lclactivity);
  public native int LCLOnMessageBoxFinished(int Result, int DialogType);
  public native int LCLOnKey(int kind, int keyCode, KeyEvent event, int AChar);
  public native int LCLOnTimer(Runnable timerid, int timeridindex);
  public native int LCLOnConfigurationChanged(int ANewDPI, int ANewWidth);
  public native int LCLOnSensorChanged(int ASensorKind, double[] AValues);
  public native int LCLOnMenuAction(int kind, int itemIndex);

  // -------------------------------------------
  // Functions exported to the Pascal side
  // -------------------------------------------

  // input: String lcltext, int lcltextsize
  // output: int lclwidth, int lclheight, int lclascent, etc
  public void LCLDoGetTextBounds()
  {
    Paint localpaint = new Paint();
    Rect localbounds = new Rect();
    String lcltext_hack = "M" + lcltext + "M";
    localpaint.setTextSize(lcltextsize);
    // Paint.getTextBounds has multiple problems:
    // 1->It consistently gives us a too small size
    // 2->It ignores spaces in the end and has issues with some characters, see http://code.google.com/p/android/issues/detail?id=7527
    // so we use measureText instead, but it also has problem 2, so we need to add a character to the start and end of text and then subtract it =(
    lclwidth = (int) (localpaint.measureText(lcltext_hack) - localpaint.measureText("MM"));
    // Painter.getTextBounds consistently gives us a too small size so work around that
//    lclwidth = lclwidth + (3 * lcltextsize) / 16;
    // Don't use just localbounds.height() from the source text
    // because it will calculate the minimum necessary height,
    // but we can't easily use that to draw text because it draws relative to the baseline
    localpaint.getTextBounds("Íqg", 0, 3, localbounds);
    lclheight = localbounds.height();
    // Also get some measures
    lcltextascent = (int) localpaint.getFontMetrics().ascent;
    lcltextbottom = (int) localpaint.getFontMetrics().bottom;
    lcltextdescent = (int) localpaint.getFontMetrics().descent;
    lcltextleading = (int) localpaint.getFontMetrics().leading;
    lcltexttop = (int) localpaint.getFontMetrics().top;
  }

  // input: String lcltext, int lclmaxwidth, int lcltextsize
  // output: int lclmaxcount
  public void LCLDoGetTextPartialWidths()
  {
    Paint localpaint = new Paint();
    Rect localbounds = new Rect();
    localpaint.setTextSize(lcltextsize);

    float localmaxwidth = (float) lclmaxwidth;
    //Log.i("lclapp", "[LCLDoGetTextPartialWidths] lcltext="+lcltext+" localmaxwidth="+Float.toString(localmaxwidth));
    lclmaxcount = localpaint.breakText(lcltext, true, localmaxwidth, lclpartialwidths);
  }

  // input: String lcltext, int lclwidth, int lclheight
  // output: lclbitmap
  public void LCLDoDrawText(int ATextColor)
  {
    lclbitmap = Bitmap.createBitmap(lclwidth, lclheight, Bitmap.Config.ARGB_8888);
    Canvas localcanvas = new Canvas(lclbitmap);
    Paint localpaint = new Paint();
    localpaint.setColor(ATextColor);
    localpaint.setTextSize(lcltextsize);
    localpaint.setFlags(Paint.ANTI_ALIAS_FLAG);
    localcanvas.drawColor(Color.TRANSPARENT); // TRANSPARENT
    // The Y coordinate is the lower baseline of letters like "abc"
    // see http://code.google.com/p/android/issues/detail?id=393
    localcanvas.drawText(lcltext, 0, lclheight - lcltextbottom, localpaint);
  }

  // LCLType definitions

  private final int idButtonBase = 0x00000000;
  private final int idButtonOk = 0x00000001;
  private final int idButtonCancel = 0x00000002;
  private final int idButtonHelp = 0x00000003;
  private final int idButtonYes = 0x00000004;
  private final int idButtonNo = 0x00000005;
  private final int idButtonClose = 0x00000006;
  private final int idButtonAbort = 0x00000007;
  private final int idButtonRetry = 0x00000008;
  private final int idButtonIgnore = 0x00000009;
  private final int idButtonAll = 0x0000000A;
  private final int idButtonYesToAll = 0x0000000B;
  private final int idButtonNoToAll = 0x0000000C;
  private final int idButtonOpen = 0x0000000D;
  private final int idButtonSave = 0x0000000E;
  private final int idButtonShield = 0x0000000F;

  // input: String lcltext, String lcltitle, int lclconfig (buttons)
  // output: nothing, but calles LCLOnMessageBoxFinished
  public void LCLDoShowMessageBox()
  {
    DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener()
    {
      @Override
      public void onClick(DialogInterface dialog, int which)
      {
        switch (which)
        {
        case DialogInterface.BUTTON_POSITIVE:
          LCLOnMessageBoxFinished(lclbutton1, 0);
          break;
        case DialogInterface.BUTTON_NEUTRAL:
          LCLOnMessageBoxFinished(lclbutton2, 0);
          break;
        case DialogInterface.BUTTON_NEGATIVE:
          LCLOnMessageBoxFinished(lclbutton3, 0);
          break;
        };
      }
    };

    DialogInterface.OnCancelListener dialogCancelListener = new DialogInterface.OnCancelListener()
    {
      @Override
      public void onCancel(DialogInterface dialog)
      {
        // The Cancel button number matches for LCLIntf.MessageBox and LCLIntf.PromptDialog
        LCLOnMessageBoxFinished(idButtonCancel, 0);
      }
    };

    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    builder.setMessage(lcltext);
    builder.setTitle(lcltitle);
    if (lclbutton1 >= 0) builder.setPositiveButton(lclbutton1str, dialogClickListener);
    if (lclbutton2 >= 0) builder.setNeutralButton(lclbutton2str, dialogClickListener);
    if (lclbutton3 >= 0) builder.setNegativeButton(lclbutton3str, dialogClickListener);
    builder.show().setOnCancelListener(dialogCancelListener);
  };

  private Handler LocalHandler = new Handler();

  private class LCLRunnable implements Runnable
  {
    public boolean Destroyed = false;

    public void run()
    {
      int lcltimeridindex = lcltimerids.indexOf(this);
      int eventResult = LCLOnTimer(this, lcltimeridindex);
      ProcessEventResult(eventResult);
      if (this.Destroyed == false) LocalHandler.postDelayed(this, lcltimerinterval);
    }
  };

  // input:  int lcltimerinterval in milliseconds
  // output:  Runnable lcltimerid
  public void LCLDoCreateTimer()
  {
    lcltimerid = new LCLRunnable();

    LocalHandler.removeCallbacks(lcltimerid);
    LocalHandler.postDelayed(lcltimerid, lcltimerinterval);

    lcltimerids.add(lcltimerid);
  };

  // input: Runnable lcltimerid
  public void LCLDoDestroyTimer()
  {
    LocalHandler.removeCallbacks(lcltimerid);
    ((LCLRunnable) lcltimerid).Destroyed = true;
    lcltimerids.remove(lcltimerids.indexOf(lcltimerid));
  };

  public void LCLDoHideVirtualKeyboard()
  {
    InputMethodManager localInputManager = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
    localInputManager.hideSoftInputFromWindow(lclsurface.getWindowToken(), 0);
  };

  public void LCLDoShowVirtualKeyboard()
  {
    InputMethodManager localInputManager = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
    localInputManager.showSoftInput(lclsurface, 0);
  };

  // SensorEventListener overrides

  @Override public void onSensorChanged(SensorEvent event)
  {
    double[] eventValues = convertFloatsToDoubles(event.values);
    int eventKind = event.sensor.getType();
    int eventResult = LCLOnSensorChanged(eventKind, eventValues);
    if (((eventResult | 1) != 0) && (lclsurface != null)) lclsurface.postInvalidate();
  }

  @Override public void onAccuracyChanged(Sensor sensor, int accuracy)
  {
  }

  public void LCLDoStartReadingAccelerometer()
  {
    localSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
    localSensorManager.registerListener(this,
      localSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
      SensorManager.SENSOR_DELAY_NORMAL);
  };

  public void LCLDoStopReadingAccelerometer()
  {
    localSensorManager.unregisterListener(this);
  };

  // input: String lcldestination, String lcltext (Body)
  public void LCLDoSendMessage()
  {
    if (lclkind == 1)
    {
      PendingIntent sentPI = PendingIntent.getBroadcast(this, 0,
        new Intent("SMS_SENT"), 0);

      PendingIntent deliveredPI = PendingIntent.getBroadcast(this, 0,
        new Intent("SMS_DELIVERED"), 0);

      //---when the SMS has been sent---
      registerReceiver(new BroadcastReceiver()
      {
        @Override public void onReceive(Context arg0, Intent arg1)
        {
          double[] statusArray = new double[1];
          int eventResult = 0;
          switch (getResultCode())
          {
            case Activity.RESULT_OK:
              statusArray[0] = 1.0;
              eventResult = LCLOnSensorChanged(-11, statusArray);
              break;
            case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
              statusArray[0] = 2.0;
              eventResult = LCLOnSensorChanged(-11, statusArray);
              break;
            case SmsManager.RESULT_ERROR_NO_SERVICE:
              statusArray[0] = 3.0;
              eventResult = LCLOnSensorChanged(-11, statusArray);
              break;
            case SmsManager.RESULT_ERROR_NULL_PDU:
              statusArray[0] = 2.0;
              eventResult = LCLOnSensorChanged(-11, statusArray);
              break;
            case SmsManager.RESULT_ERROR_RADIO_OFF:
              statusArray[0] = 5.0;
              eventResult = LCLOnSensorChanged(-11, statusArray);
              break;
          }
          ProcessEventResult(eventResult);
        }
      }, new IntentFilter("SMS_SENT"));

      //---when the SMS has been delivered---
      registerReceiver(new BroadcastReceiver()
      {
        @Override public void onReceive(Context arg0, Intent arg1)
        {
          double[] statusArray = new double[1];
          int eventResult = 0;
          switch (getResultCode())
          {
            case Activity.RESULT_OK:
              statusArray[0] = 10.0;
              eventResult = LCLOnSensorChanged(-11, statusArray);
              break;
            case Activity.RESULT_CANCELED:
              statusArray[0] = 11.0;
              eventResult = LCLOnSensorChanged(-11, statusArray);
              break;
          }
          ProcessEventResult(eventResult);
        }
      }, new IntentFilter("SMS_DELIVERED"));

      // SMS sending seams to cause an awful lot of exceptions
      // See: http://stackoverflow.com/questions/4580952/why-do-i-get-nullpointerexception-when-sending-an-sms-on-an-htc-desire-or-what
      // See: http://code.google.com/p/android/issues/detail?id=3718
      try
      {
        SmsManager sms = SmsManager.getDefault();
        Log.i("lclapp", "[LCLDoSendMessage] lcldestination="+lcldestination+" lcltext="+lcltext);
        ArrayList<String> parts = sms.divideMessage(lcltext);
        //sms.sendMultipartTextMessage(lcldestination, null, parts, sentPI, deliveredPI);
        sms.sendTextMessage(lcldestination, null, lcltext, sentPI, deliveredPI);
      }
      catch (Exception e)
      {
      }
    }
  };

  // LocationListener overrides

  @Override public void onLocationChanged(Location loc)
  {
    if (loc != null)
    {
      double[] positionArray = new double[6];
      positionArray[0] = loc.getLatitude();
      positionArray[1] = loc.getLongitude();
      positionArray[2] = loc.getAltitude();
      positionArray[3] = (double)loc.getAccuracy();
      positionArray[4] = (double)loc.getSpeed();
      positionArray[5] = (double)loc.getTime();
      int eventResult = LCLOnSensorChanged(-10, positionArray);
      if (((eventResult | 1) != 0) && (lclsurface != null)) lclsurface.postInvalidate();
    }
  }

  @Override public void onProviderDisabled(String provider)
  {
  }

  @Override public void onProviderEnabled(String provider)
  {
  }

  @Override public void onStatusChanged(String provider, int status, Bundle extras)
  {
  }

  // input:  int lclkind
  public void LCLDoRequestPositionInfo()
  {
    LocationManager mlocManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
    switch (lclkind)
    {
      case 1:
        mlocManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);
        break;
      case 2:
        mlocManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, this);
        break;
      default:
        Log.i("lclapp", "[LCLDoRequestPositionInfo] Wrong lclkind parameter");
        break;
    }
  }

  // Prepare dialog callbacks
  // for TCDComboBox
  public void LCLDoPrepareSelectItemDialog(CharSequence[] items, int selected, AlertDialog.Builder dialog)
  {
    //Log.i("lclapp", "LCLDoPrepareSelectItemDialog");
    dialog.setSingleChoiceItems(items, selected, new DialogInterface.OnClickListener()
    {
      @Override public void onClick(DialogInterface dialog, int which)
      {
        //Log.i("lclapp", "LCLDoPrepareSelectItemDialog.onClick");
        LCLOnMessageBoxFinished(which, 1);
        dialog.dismiss();
      }
    });
  }

  // This method allows us to use the native Android ListView in a dialog
  // It is nice for choosing a line in a table with multiple columns of information
  // The extra columns appear as sub-info in the Android ListView
  //
  // output: Calls LCLOnMessageBoxFinished which will call LCLIntf.OnListViewDialogResult
  //
  public void LCLDoShowListViewDialog(String ATitle, String[] AItems,
    String[] ASubItems, int AColorOddRow, int AColorEvenRow)
  {
    final Dialog dialog = new Dialog(this);

    ListView lListView = new ListView(this);
    List<LCL_ListViewItem> listItems = new ArrayList<LCL_ListViewItem>();
    for (int i = 0; i < AItems.length; i++)
    {
      listItems.add(new LCL_ListViewItem(AItems[i], ASubItems[i]));
    };
    LCL_ListViewAdapter listAdapter = new LCL_ListViewAdapter(
      this,
      listItems,
      android.R.layout.simple_list_item_2,
      new String[] { "title", "description" },
      new int[] { android.R.id.text1, android.R.id.text2 });
    listAdapter.colors[0] = AColorOddRow;
    listAdapter.colors[1] = AColorEvenRow;
    lListView.setAdapter(listAdapter);
    lListView.setClickable(true);
    AdapterView.OnItemClickListener listviewClickListener = new AdapterView.OnItemClickListener()
    {
      @Override public void onItemClick(AdapterView<?> arg0, View arg1, int position, long arg3)
      {
        //Log.i("lclapp", "LCLDoPrepareSelectItemDialog.onClick");
        LCLOnMessageBoxFinished(position, 2);
        dialog.dismiss();
      }
    };
    lListView.setOnItemClickListener(listviewClickListener);

    DialogInterface.OnCancelListener dialogCancelListener = new DialogInterface.OnCancelListener()
    {
      @Override public void onCancel(DialogInterface dialog)
      {
        LCLOnMessageBoxFinished(-1, 2);
      }
    };
    dialog.setOnCancelListener(dialogCancelListener);
    dialog.setTitle(ATitle);
    dialog.setContentView(lListView);
    dialog.show();
  }

  //
  // Classes for the ListView
  //

  //
  // ListView item
  //
  public class LCL_ListViewItem extends HashMap<String, String>
  {
    public String Title;
    public String Description;

    public LCL_ListViewItem(String ATitle, String ADescription)
    {
      this.Title = ATitle;
      this.Description = ADescription;
    }

    @Override public String get(Object k)
    {
      String key = (String) k;
      if (key.equals("title")) return Title;
      else if (key.equals("description")) return Description;
      return null;
    }
  }

  //
  // Adapter class for the ListView
  //
  public class LCL_ListViewAdapter extends SimpleAdapter
  {
    private List<LCL_ListViewItem> Items;
    // Colors to alternate
    public int[] colors = new int[] { 0xff292C29, 0xff424542 };

    @SuppressWarnings("unchecked") public LCL_ListViewAdapter(
      Context context,
      List<? extends Map<String, String>> AItems,
      int resource,
      String[] from,
      int[] to)
    {
      super(context, AItems, resource, from, to);
      this.Items = (List<LCL_ListViewItem>) Items;
    }

    @Override public View getView(int position, View convertView, ViewGroup parent)
    {
      View view = super.getView(position, convertView, parent);

      int colorPos = position % colors.length;
      view.setBackgroundColor(colors[colorPos]);
      return view;
    }
  }

  // -------------------------------------------
  // End of the helper classes of LCLDoShowListViewDialog
  // -------------------------------------------

  // -------------------------------------------
  // Fields exported to the Pascal side for easier data communication
  // -------------------------------------------
  public String lcltext;
  public String lcltitle;
  public String lclbutton1str;
  public String lclbutton2str;
  public String lclbutton3str;
  //
  public int lclwidth;
  public int lclheight;
  public int lclbutton1;
  public int lclbutton2;
  public int lclbutton3;
  public Bitmap lclbitmap;
  //
  public int lcltextsize;
  public int lcltextascent;
  public int lcltextbottom;
  public int lcltextdescent;
  public int lcltextleading;
  public int lcltexttop;
  public int lclmaxwidth;
  public int lclmaxcount;
  public float[] lclpartialwidths;
  //
  public int lcltimerinterval;
  public Runnable lcltimerid;
  public List lcltimerids = new ArrayList(); // To keep the references alive, avoids a wrong GC
  //
  public int lclxdpi;
  public int lclydpi;
  public int lclformwidth;
  public int lclformheight;
  public int lclscreenwidth;
  public int lclscreenheight;
  // for LazDeviceAPIs
  public String lcldestination;
  public int lclkind;
  // for the menus
  public String[] lclmenu_captions = new String[6];
  public boolean flagIsMenuOpen = false;

  static
  {
    try 
    {
      Log.i("lclapp", "Trying to load liblclapp.so");
      System.loadLibrary("lclapp");
    } 
    catch(UnsatisfiedLinkError ule) 
    {
      Log.e("lclapp", "WARNING: Could not load liblclapp.so");
      ule.printStackTrace();
    }
  }
}
