commit cab1d9c17f1a5eaabd0e4b6a625ffb1bf35616c9
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Sun Jul 17 23:13:36 2022 +0200

    Add Crashlytics, Analytics, In-App Messaging and Cloud Messaging.

diff --git a/build.gradle b/build.gradle
index 5a7c7fc..9d1166b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -9,6 +9,9 @@ buildscript {
 
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
+
+        classpath 'com.google.gms:google-services:4.3.13'
+        classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.1'
     }
 }
 
diff --git a/distorted-sokoban/build.gradle b/distorted-sokoban/build.gradle
index fe79622..58ec2f4 100644
--- a/distorted-sokoban/build.gradle
+++ b/distorted-sokoban/build.gradle
@@ -1,4 +1,6 @@
 apply plugin: 'com.android.application'
+apply plugin: 'com.google.gms.google-services'
+apply plugin: 'com.google.firebase.crashlytics'
 
 android {
     compileSdkVersion 31
@@ -6,7 +8,7 @@ android {
 
     defaultConfig {
         applicationId "org.distorted.sokoban"
-        minSdkVersion 7
+        minSdkVersion 21
         targetSdkVersion 31
     }
 
@@ -17,3 +19,13 @@ android {
         }
     }
 }
+
+dependencies {
+    implementation platform('com.google.firebase:firebase-bom:30.2.0')
+    implementation 'com.google.firebase:firebase-messaging'
+    implementation 'com.google.firebase:firebase-analytics'
+    implementation 'com.google.firebase:firebase-crashlytics'
+    implementation 'com.google.firebase:firebase-inappmessaging-display'
+    implementation "androidx.work:work-runtime:2.7.1"
+}
+
diff --git a/distorted-sokoban/src/main/AndroidManifest.xml b/distorted-sokoban/src/main/AndroidManifest.xml
index 4dfe802..88bd1d9 100644
--- a/distorted-sokoban/src/main/AndroidManifest.xml
+++ b/distorted-sokoban/src/main/AndroidManifest.xml
@@ -10,6 +10,8 @@
                         android:anyDensity="true"/>
     
     <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+
     <uses-feature android:name="android.hardware.touchscreen"/>
     
     <application android:icon="@drawable/icon" android:label="@string/app_name">
diff --git a/distorted-sokoban/src/main/java/org/distorted/keyboard/TextFieldArea.java b/distorted-sokoban/src/main/java/org/distorted/keyboard/TextFieldArea.java
new file mode 100644
index 0000000..2466b62
--- /dev/null
+++ b/distorted-sokoban/src/main/java/org/distorted/keyboard/TextFieldArea.java
@@ -0,0 +1,144 @@
+package org.distorted.sokoban;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+
+///////////////////////////////////////////////////////////////////
+
+public class TextFieldArea 
+    {
+	//private static final String TAG_TEXT = "RRText";
+	
+    private StringBuffer text;
+ 
+    private int textFieldBkgColor    = 0xFFEEEEEE;
+    private int textFieldBorderColor = 0xFF000000;
+    private int textFieldFontColor   = 0xFF000000;
+   
+    private int maxSize = -1;
+    private int strLen;
+    private Paint mPaint;
+    
+///////////////////////////////////////////////////////////////////
+    
+    public TextFieldArea() 
+    {
+        text   = new StringBuffer();
+        strLen = 0;
+        mPaint = new Paint();
+        mPaint.setAntiAlias(true);
+    }
+
+///////////////////////////////////////////////////////////////////
+    
+    public void deleteLastChar() 
+    {
+        if (strLen > 0) 
+        {
+            text.deleteCharAt(strLen-1);
+            strLen--;
+        }
+    }
+
+///////////////////////////////////////////////////////////////////
+    
+    public void insertNewChar(char aChar) 
+    {
+        if ( ((maxSize > 0) && (text.length() < maxSize)) || (maxSize < 0)) 
+        { 
+            if (aChar != 0) 
+            {
+                text.insert(strLen, aChar);
+                strLen++;
+            }
+        }
+    }
+
+///////////////////////////////////////////////////////////////////
+    
+    public String getText() 
+    {
+        return text.toString();
+    }
+
+///////////////////////////////////////////////////////////////////
+    
+    public void setText(String newText) 
+    {
+        text   = null;
+        text   = new StringBuffer(newText);
+        strLen = newText.length();
+    }
+
+///////////////////////////////////////////////////////////////////
+   
+    public void clearTextField() 
+    {
+        text = null;
+        text = new StringBuffer();
+        strLen = 0;
+    }
+
+///////////////////////////////////////////////////////////////////
+    
+    public void paint( Canvas canvas, int left, int top, int right, int bottom) 
+    {
+    	int fontH = (int)((bottom-top)*0.7);
+    	
+        mPaint.setColor(textFieldBkgColor);
+        mPaint.setStyle(Style.FILL);
+        canvas.drawRect(left, top, right, bottom, mPaint);
+        mPaint.setColor(textFieldBorderColor);
+        mPaint.setStyle(Style.STROKE);
+        canvas.drawRect(left, top, right, bottom, mPaint);
+        mPaint.setColor(textFieldFontColor);
+        mPaint.setTextSize(fontH);   
+        
+        String t = text.toString();
+        
+        while( mPaint.measureText(t) > right-left-(bottom-top)/2 )
+          {
+          mPaint.setTextSize(--fontH);
+          }
+        
+        canvas.drawText( t, left+(bottom-top)/4, (bottom+top+fontH)/2-fontH/8, mPaint);
+    }
+
+////////////////////////////////////////////////////////////////////////////////
+    
+    public void setTextFieldBkgColor(int textFieldBkgColor) 
+    {
+        this.textFieldBkgColor = textFieldBkgColor;
+    }
+
+////////////////////////////////////////////////////////////////////////////////
+    
+    public void setTextFieldBorderColor(int textFieldBorderColor)
+    {
+        this.textFieldBorderColor = textFieldBorderColor;
+    }
+
+////////////////////////////////////////////////////////////////////////////////
+ 
+    public void setTextFieldFontColor(int textFieldFontColor) 
+    {
+        this.textFieldFontColor = textFieldFontColor;
+    }
+
+////////////////////////////////////////////////////////////////////////////////
+
+    public int getMaxSize() 
+    {
+        return maxSize;
+    }
+
+////////////////////////////////////////////////////////////////////////////////
+  
+    public void setMaxSize(int maxSize) 
+    {
+        this.maxSize = maxSize;
+    }
+////////////////////////////////////////////////////////////////////////////////
+// end of TextFieldArea    
+}
diff --git a/distorted-sokoban/src/main/java/org/distorted/keyboard/TouchKeyboard.java b/distorted-sokoban/src/main/java/org/distorted/keyboard/TouchKeyboard.java
new file mode 100644
index 0000000..2dc71e3
--- /dev/null
+++ b/distorted-sokoban/src/main/java/org/distorted/keyboard/TouchKeyboard.java
@@ -0,0 +1,459 @@
+package org.distorted.sokoban;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.graphics.Paint.Style;
+import android.graphics.drawable.NinePatchDrawable;
+
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+
+//import android.util.Log;
+
+////////////////////////////////////////////////////////////////////////////////
+
+public class TouchKeyboard extends View
+  {
+  //private static final String TAG_KEYBOARD = "RubikKeyboard";    
+       
+  private static final int  MARGIN = 2;
+  private static final int WMARGIN = 8;
+  private static final int BUTTON_NONE = -4;
+  private static final int BUTTON_OK   = -3;
+  private static final int BUTTON_BKSP = -2;
+  private static final int BUTTON_CAPS = -1;
+ 
+  private int currPressed;
+ 
+  private TextFieldArea textField;
+  private Paint mPaint;
+  private NinePatchDrawable keyBlue, keyRed, keyWhite;
+  private Bitmap ok, bksp, caps;
+ 
+  private int bmpSize;
+ 
+  char[][] lowerCaseL =
+      { {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'},
+        {'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r'},
+        {'s', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0'},
+        {'1', '2', '3', '4', '5', '6', '7', '8', '9'}
+      };
+   
+  char[][] upperCaseL =
+      { {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'},
+        {'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R'},
+        {'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0'},
+        {'1', '2', '3', '4', '5', '6', '7', '8', '9'}
+      };
+ 
+  char[][] lowerCaseP =
+      { {'a', 'b', 'c', 'd', 'e', 'f'},
+        {'g', 'h', 'i', 'j', 'k', 'l'},
+        {'m', 'n', 'o', 'p', 'q', 'r'},
+        {'s', 't', 'u', 'v', 'w', 'x'},
+        {'y', 'z', '0', '1', '2', '3'},
+        {'4', '5', '6', '7', '8', '9'}
+      };
+
+  char[][] upperCaseP =
+      { {'A', 'B', 'C', 'D', 'E', 'F'},
+        {'G', 'H', 'I', 'J', 'K', 'L'},
+        {'M', 'N', 'O', 'P', 'Q', 'R'},
+        {'S', 'T', 'U', 'V', 'W', 'X'},
+        {'Y', 'Z', '0', '1', '2', '3'},
+        {'4', '5', '6', '7', '8', '9'}
+      };
+ 
+  private int width, height;
+ 
+  private int textFieldWidth;  
+  private int textFieldHeight;
+  private int textTopLeftX;  
+  private int textTopLeftY;
+  private int keyWidth, keyHeight;
+  private int sKeyWidth, sKeyHeight;
+     
+  private VirtualKeyboardListener invoker;
+   
+  private int charH;
+   
+  private int backgroundColor = 0xffffffff;
+  private int buttonsColor    = 0xff000000;
+ 
+  private boolean uppercase;
+  private int minSize=0;
+ 
+  public interface VirtualKeyboardListener
+    {    
+    void okPressed(String string);
+    }
+ 
+////////////////////////////////////////////////////////////////////////////////
+
+  public TouchKeyboard(Context context, int scrWid, int scrHei)
+    {
+    super(context);
+    mPaint = new Paint();
+    mPaint.setAntiAlias(true);
+   
+    currPressed = BUTTON_NONE;
+    width = scrWid;
+    height= scrHei;
+   
+    Resources res = context.getResources();
+
+    keyBlue = (NinePatchDrawable)res.getDrawable(R.drawable.keys_blue);
+    keyRed  = (NinePatchDrawable)res.getDrawable(R.drawable.keys_red);
+    keyWhite= (NinePatchDrawable)res.getDrawable(R.drawable.keys_white);
+   
+    ok  = BitmapFactory.decodeResource(res, R.drawable.ok  );
+    bksp= BitmapFactory.decodeResource(res, R.drawable.bksp);
+    caps= BitmapFactory.decodeResource(res, R.drawable.caps);
+   
+    if( width<=height )
+      {
+      sKeyWidth = width/3;
+      sKeyHeight= width/5;
+      keyWidth  = width/6;
+      keyHeight = (height-2*sKeyHeight)/6;
+     
+      textFieldHeight = height-sKeyHeight-6*keyHeight;
+      textFieldWidth = width;
+      textTopLeftX = 0;
+      textTopLeftY = sKeyHeight;
+      }
+    else
+      {
+      sKeyWidth = sKeyHeight = keyHeight = height/5;
+      keyWidth = (width+1)/9;
+               
+      textFieldHeight= sKeyHeight;
+      textFieldWidth = width-3*sKeyWidth;
+      textTopLeftX = sKeyWidth;
+      textTopLeftY = 0;
+      }
+ 
+    bmpSize = ok.getHeight();
+   
+    if( bmpSize> 0.8*sKeyHeight )
+      {
+      bmpSize = (int)(0.8*sKeyHeight);
+      ok   = Bitmap.createScaledBitmap(ok  , bmpSize, bmpSize, true);
+      bksp = Bitmap.createScaledBitmap(bksp, bmpSize, bmpSize, true);
+      caps = Bitmap.createScaledBitmap(caps, bmpSize, bmpSize, true);
+      }
+   
+    textField = new TextFieldArea();
+    charH= (int)(keyHeight*0.7);  
+    uppercase = true;
+    }
+
+////////////////////////////////////////////////////////////////////////////////
+
+  public void resetKeyBoard()
+    {
+    textField.clearTextField();
+    invalidate();
+    }
+
+////////////////////////////////////////////////////////////////////////////////
+
+  protected void onDraw(Canvas c)
+    {      
+    mPaint.setColor(backgroundColor);
+    mPaint.setStyle(Style.FILL);
+    mPaint.setTextSize(charH);
+    mPaint.setTextAlign(Align.CENTER);
+   
+    c.drawRect(0, 0, width, height, mPaint);
+   
+    mPaint.setColor(buttonsColor);
+   
+    textField.paint( c, textTopLeftX+1, textTopLeftY+MARGIN/2+1, textTopLeftX+textFieldWidth-1, textTopLeftY+textFieldHeight-MARGIN/2-1);
+   
+    if( currPressed==BUTTON_OK )
+      {
+      keyRed.setBounds( MARGIN/2, MARGIN/2, sKeyWidth-MARGIN/2, sKeyHeight-MARGIN/2);
+      keyRed.draw(c);
+      }
+    else
+      {
+      keyBlue.setBounds( MARGIN/2, MARGIN/2,sKeyWidth-MARGIN/2, sKeyHeight-MARGIN/2);
+      keyBlue.draw(c); 
+      }
+   
+    if( currPressed==BUTTON_BKSP )
+      {
+      keyRed.setBounds( width-2*sKeyWidth+MARGIN/2, MARGIN/2, width-sKeyWidth-MARGIN/2,sKeyHeight-MARGIN/2);
+      keyRed.draw(c);
+      }
+    else
+      {
+      keyBlue.setBounds(width-2*sKeyWidth+MARGIN/2, MARGIN/2, width-sKeyWidth-MARGIN/2,sKeyHeight-MARGIN/2);
+      keyBlue.draw(c); 
+      }
+   
+    if( currPressed==BUTTON_CAPS )
+      {
+      keyRed.setBounds( width-sKeyWidth+MARGIN/2, MARGIN/2, width-MARGIN/2,sKeyHeight-MARGIN/2);
+      keyRed.draw(c);
+      }
+    else
+      {
+      keyBlue.setBounds(width-sKeyWidth+MARGIN/2, MARGIN/2, width-MARGIN/2,sKeyHeight-MARGIN/2);
+      keyBlue.draw(c); 
+      }
+   
+    c.drawBitmap(ok  ,                   MARGIN/2+sKeyWidth/2-bmpSize/2, MARGIN/2+sKeyHeight/2-bmpSize/2, mPaint);
+    c.drawBitmap(bksp, width-2*sKeyWidth+MARGIN/2+sKeyWidth/2-bmpSize/2, MARGIN/2+sKeyHeight/2-bmpSize/2, mPaint);
+    c.drawBitmap(caps, width-  sKeyWidth+MARGIN/2+sKeyWidth/2-bmpSize/2, MARGIN/2+sKeyHeight/2-bmpSize/2, mPaint);
+   
+    int hei = (width<=height ? textFieldHeight+sKeyHeight : textFieldHeight);
+    int numCol = (width<=height ? lowerCaseP[0].length : lowerCaseL[0].length);
+    int numRow = (width<=height ? lowerCaseP.length : lowerCaseL.length);
+   
+    for(int j=0; j<numRow; j++)
+      {
+      for(int i=0; i<numCol; i++)
+        {  
+        if( currPressed==j*numCol+i )
+          {
+          keyRed.setBounds(  i*width/numCol+ MARGIN/2, hei+ MARGIN/2,
+                                     (i+1)*width/numCol- MARGIN/2, hei+ MARGIN/2 +keyHeight );
+          keyRed.draw(c);
+          keyWhite.setBounds(i*width/numCol+WMARGIN/2, hei+WMARGIN/2-keyHeight,
+                                 (i+1)*width/numCol-WMARGIN/2, hei-WMARGIN/2 );
+          keyWhite.draw(c);              
+         
+          if( width<=height )
+            c.drawText( uppercase? upperCaseP[j]:lowerCaseP[j], i,1,(float)(i*width/numCol+keyWidth/2),
+                        (float)(hei-keyHeight/2+charH*0.4), mPaint);
+          else
+                c.drawText( uppercase? upperCaseL[j]:lowerCaseL[j], i,1,(float)(i*width/numCol+keyWidth/2),
+                        (float)(hei-keyHeight/2+charH*0.4), mPaint);  
+          }
+        else
+          {
+          keyBlue.setBounds(  i*width/numCol+ MARGIN/2, hei+ MARGIN/2,
+                                  (i+1)*width/numCol- MARGIN/2, hei+ MARGIN/2 +keyHeight );
+          keyBlue.draw(c);
+          }
+       
+        if( width<=height )
+            c.drawText( uppercase? upperCaseP[j]:lowerCaseP[j], i,1,(float)(i*width/numCol+keyWidth/2),
+                        (float)(hei+keyHeight/2+charH*0.4), mPaint);
+        else
+            c.drawText( uppercase? upperCaseL[j]:lowerCaseL[j], i,1,(float)(i*width/numCol+keyWidth/2),
+                        (float)(hei+keyHeight/2+charH*0.4), mPaint);
+        }
+      hei+=keyHeight;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////
+
+  public boolean onTouchEvent(MotionEvent event)
+    {
+    switch(event.getAction())
+      {
+      case MotionEvent.ACTION_DOWN: buttonPressed( (int)event.getX(), (int)event.getY() );
+                                    break;
+      case MotionEvent.ACTION_UP  : buttonReleased();
+                                    break;
+      case MotionEvent.ACTION_MOVE: buttonPressed( (int)event.getX(), (int)event.getY() );
+                                    break;
+      }
+
+    invalidate();
+    return true;
+    }
+
+////////////////////////////////////////////////////////////////////////////////
+
+  private void buttonPressed(int x, int y)
+    {    
+    if( width<height )
+        {
+        if( y>=0 && y<sKeyHeight )
+              {
+                   if( x<=  sKeyWidth ) currPressed = BUTTON_OK;
+              else if( x<=2*sKeyWidth ) currPressed = BUTTON_BKSP;
+              else                      currPressed = BUTTON_CAPS;
+              }
+        else if( y>=sKeyHeight+textFieldHeight && y<=height )
+              {
+              currPressed = (int)((y-sKeyHeight-textFieldHeight)/keyHeight)*lowerCaseP[0].length + x/keyWidth;
+              }
+        }
+    else
+        {
+        if( y>=0 && y<textFieldHeight )
+              {
+                   if( x<=sKeyWidth )                               currPressed = BUTTON_OK;
+              else if( x>=width-2*sKeyWidth && x<=width-sKeyWidth ) currPressed = BUTTON_BKSP;
+              else if( x>=width- sKeyWidth && x<=width )            currPressed = BUTTON_CAPS;
+              else                                                  currPressed = BUTTON_NONE;
+              }
+        else if( y>=textFieldHeight && y<=height)
+              {
+              currPressed = (int)((y-textFieldHeight)/keyHeight)*lowerCaseL[0].length + x/keyWidth;
+              }
+        }
+    }
+
+////////////////////////////////////////////////////////////////////////////////
+
+  private void buttonReleased()
+    {
+         if( currPressed==BUTTON_OK   ) okButtonAction();
+    else if( currPressed==BUTTON_BKSP ) bkspButtonAction();  
+    else if( currPressed==BUTTON_CAPS ) capsButtonAction();  
+    else if( currPressed>=0           ) charAreaAction(currPressed);        
+               
+    currPressed = BUTTON_NONE;
+    }
+
+////////////////////////////////////////////////////////////////////////////////
+ 
+  @Override public boolean onKeyDown(int keyCode, KeyEvent event)
+    {
+    if( currPressed!=BUTTON_NONE ) return false;
+   /*
+    switch( keyCode )
+      {
+      case Keypad.KEY_ALT        :
+      case Keypad.KEY_SHIFT_RIGHT:
+      case Keypad.KEY_SHIFT_LEFT : currPressed= BUTTON_CAPS; invalidate(); break;
+      case Keypad.KEY_ENTER      : currPressed= BUTTON_OK  ; invalidate(); break;
+      case Keypad.KEY_DELETE     :
+      case Keypad.KEY_BACKSPACE  : currPressed= BUTTON_BKSP; invalidate(); break;
+      }
+   
+    if( key>='A' && key<='Z' ) { currPressed = key-'A'          ; invalidate(); }
+    if( key>='a' && key<='z' ) { currPressed = key-'a'          ; invalidate(); }
+    if( key>='0' && key<='9' ) { currPressed = 'z'-'a'+1+key-'0'; invalidate(); }
+   */
+    return true;
+    }
+   
+////////////////////////////////////////////////////////////////////////////////
+   
+  @Override public boolean onKeyUp(int keyCode, KeyEvent event)
+    {
+    buttonReleased();
+    return true;
+    }
+   
+////////////////////////////////////////////////////////////////////////////////
+
+  private void capsButtonAction()
+    {
+    uppercase = !uppercase;
+    }
+
+////////////////////////////////////////////////////////////////////////////////
+
+  private void okButtonAction()
+    {
+    if ( invoker != null )
+      {
+      String text = textField.getText();
+
+      if( text.length()>= minSize )
+        invoker.okPressed(text);
+      }
+    }
+
+////////////////////////////////////////////////////////////////////////////////
+
+  private void bkspButtonAction()
+    {
+    textField.deleteLastChar();
+    }
+
+////////////////////////////////////////////////////////////////////////////////
+
+  private void charAreaAction(int pressed)
+    {    
+    int numCol = (width<height ? lowerCaseP[0].length:lowerCaseL[0].length);
+    int j = pressed/numCol;
+    int i = pressed%numCol;
+             
+    if( width<height )
+      textField.insertNewChar( uppercase ? upperCaseP[j][i]:lowerCaseP[j][i]);
+    else
+      textField.insertNewChar( uppercase ? upperCaseL[j][i]:lowerCaseL[j][i]);
+    }
+ 
+////////////////////////////////////////////////////////////////////////////////
+
+  public void setText(String text)
+    {
+    textField.setText( (text==null||text.length()==0)?"":text);
+    invalidate();
+    }
+
+////////////////////////////////////////////////////////////////////////////////
+ 
+  public void setTextFieldBkgColor(int color)
+    {
+    textField.setTextFieldBkgColor(color);  
+    }
+
+////////////////////////////////////////////////////////////////////////////////
+ 
+  public void setTextFieldBorderColor(int color)
+    {
+    textField.setTextFieldBorderColor(color);  
+    }
+
+////////////////////////////////////////////////////////////////////////////////
+ 
+  public void setTextFieldFontColor(int color)
+    {
+    textField.setTextFieldFontColor(color);  
+    }
+ 
+////////////////////////////////////////////////////////////////////////////////
+
+  public int getBackgroundColor()
+    {
+    return backgroundColor;
+    }
+ 
+////////////////////////////////////////////////////////////////////////////////
+
+  public void setBackgroundColor(int backgroundColor)
+    {
+    this.backgroundColor = backgroundColor;
+    }
+
+////////////////////////////////////////////////////////////////////////////////
+
+  public void setMaxSize(int maxSize)
+    {
+    textField.setMaxSize(maxSize);
+    }
+
+////////////////////////////////////////////////////////////////////////////////
+
+  public void setMinSize(int minSize)
+    {
+    this.minSize = minSize;
+    }
+
+////////////////////////////////////////////////////////////////////////////////
+
+  public void setVirtualKeyboardListener(VirtualKeyboardListener invoker)
+    {
+    this.invoker = invoker;
+    }
+////////////////////////////////////////////////////////////////////////////////
+// end of TouchKeyboard  
+}
\ No newline at end of file
diff --git a/distorted-sokoban/src/main/java/org/distorted/messaging/SokobanInAppMessanging.java b/distorted-sokoban/src/main/java/org/distorted/messaging/SokobanInAppMessanging.java
new file mode 100644
index 0000000..0c4e967
--- /dev/null
+++ b/distorted-sokoban/src/main/java/org/distorted/messaging/SokobanInAppMessanging.java
@@ -0,0 +1,40 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2022 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is proprietary software licensed under an EULA which you should have received      //
+// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.messaging;
+
+import com.google.firebase.inappmessaging.FirebaseInAppMessagingClickListener;
+import com.google.firebase.inappmessaging.model.Action;
+import com.google.firebase.inappmessaging.model.CampaignMetadata;
+import com.google.firebase.inappmessaging.model.InAppMessage;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class SokobanInAppMessanging implements FirebaseInAppMessagingClickListener
+{
+    @Override
+    public void messageClicked(InAppMessage inAppMessage, Action action)
+      {
+      // Determine which URL the user clicked
+      String url = action.getActionUrl();
+
+      android.util.Log.e("D", "In App Messaging: url="+url);
+
+      // Get general information about the campaign
+      CampaignMetadata metadata = inAppMessage.getCampaignMetadata();
+
+      if( metadata!=null )
+        {
+        String id = metadata.getCampaignId();
+        String name = metadata.getCampaignName();
+        boolean test = metadata.getIsTestMessage();
+        android.util.Log.e("D", "In App Messaging: id="+id+" name="+name+" test="+test);
+        }
+      }
+}
diff --git a/distorted-sokoban/src/main/java/org/distorted/messaging/SokobanMessagingService.java b/distorted-sokoban/src/main/java/org/distorted/messaging/SokobanMessagingService.java
new file mode 100644
index 0000000..4efbbbb
--- /dev/null
+++ b/distorted-sokoban/src/main/java/org/distorted/messaging/SokobanMessagingService.java
@@ -0,0 +1,95 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2022 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is proprietary software licensed under an EULA which you should have received      //
+// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.messaging;
+
+import android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import androidx.work.OneTimeWorkRequest;
+import androidx.work.WorkManager;
+import androidx.work.Worker;
+import androidx.work.WorkerParameters;
+
+import com.google.firebase.messaging.FirebaseMessagingService;
+import com.google.firebase.messaging.RemoteMessage;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class SokobanMessagingService extends FirebaseMessagingService
+{
+  private static final String TAG = "RubikMessagingService";
+
+  public static class RubikWorker extends Worker
+    {
+    public RubikWorker(@NonNull Context context, @NonNull WorkerParameters workerParams)
+      {
+      super(context, workerParams);
+      }
+
+    @NonNull
+    @Override
+    public Result doWork()
+      {
+      return Result.success();
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @Override
+  public void onMessageReceived(RemoteMessage remoteMessage)
+    {
+    Log.e(TAG, "From: " + remoteMessage.getFrom());
+
+    if (remoteMessage.getData().size() > 0)
+      {
+      Log.e(TAG, "Message data payload: " + remoteMessage.getData());
+
+      if (/* Check if data needs to be processed by long running job */ true)
+        {
+        scheduleJob();
+        }
+      else
+        {
+        handleNow();
+        }
+      }
+
+    if (remoteMessage.getNotification() != null)
+      {
+      Log.e(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @Override
+  public void onNewToken(@NonNull String token)
+    {
+    // TODO: send to my server
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void scheduleJob()
+    {
+    OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(RubikWorker.class).build();
+    WorkManager.getInstance(this).beginWith(work).enqueue();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void handleNow()
+    {
+
+    }
+}
\ No newline at end of file
diff --git a/distorted-sokoban/src/main/java/org/distorted/sokoban/Sokoban.java b/distorted-sokoban/src/main/java/org/distorted/sokoban/Sokoban.java
index 82b6fa9..27facb5 100644
--- a/distorted-sokoban/src/main/java/org/distorted/sokoban/Sokoban.java
+++ b/distorted-sokoban/src/main/java/org/distorted/sokoban/Sokoban.java
@@ -5,17 +5,22 @@ import android.content.Intent;
 import android.os.Bundle;
 import android.util.Log;
 
+import com.google.firebase.analytics.FirebaseAnalytics;
+import com.google.firebase.inappmessaging.FirebaseInAppMessaging;
+
+import org.distorted.messaging.SokobanInAppMessanging;
+
 ///////////////////////////////////////////////////////////////////
 
 public class Sokoban extends Activity 
 {
 	private static final String TAG_SPLASH = "SokobanSplash";
-    public static final boolean DEBUG = false;
-    private int sleepTime=2000;
-    
-    private class SplashThread extends Thread
+  private int sleepTime=2000;
+  private FirebaseAnalytics mFirebaseAnalytics;
+
+  private class SplashThread extends Thread
     {
-    	private boolean bootup=true;
+   private boolean bootup=true;
 		
 		public void run() 
 		{
@@ -52,22 +57,17 @@ public class Sokoban extends Activity
 ///////////////////////////////////////////////////////////////////
 	
     public void onCreate(Bundle savedInstanceState) 
-    {
-    	Log.d( TAG_SPLASH, "onCreate");
-    	
+      {
     	super.onCreate(savedInstanceState);
-        
-        if( getResources().getInteger(R.integer.is_korean) == 1 )
-  	      {
-  	      setContentView(R.layout.grb);
-          sleepTime=3500;  	
-  	      }
-  	    else
-  	      {
-  	      setContentView(R.layout.splash);
-          sleepTime=2000;  		
-  	      }
-    }
+
+  	  setContentView(R.layout.splash);
+      sleepTime=2000;
+
+      mFirebaseAnalytics = FirebaseAnalytics.getInstance(this);
+      SokobanInAppMessanging listener = new SokobanInAppMessanging();
+
+      FirebaseInAppMessaging.getInstance().addClickListener(listener);
+      }
 
 ///////////////////////////////////////////////////////////////////
 
diff --git a/distorted-sokoban/src/main/java/org/distorted/sokoban/TextFieldArea.java b/distorted-sokoban/src/main/java/org/distorted/sokoban/TextFieldArea.java
deleted file mode 100644
index 2466b62..0000000
--- a/distorted-sokoban/src/main/java/org/distorted/sokoban/TextFieldArea.java
+++ /dev/null
@@ -1,144 +0,0 @@
-package org.distorted.sokoban;
-
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Paint.Style;
-
-///////////////////////////////////////////////////////////////////
-
-public class TextFieldArea 
-    {
-	//private static final String TAG_TEXT = "RRText";
-	
-    private StringBuffer text;
- 
-    private int textFieldBkgColor    = 0xFFEEEEEE;
-    private int textFieldBorderColor = 0xFF000000;
-    private int textFieldFontColor   = 0xFF000000;
-   
-    private int maxSize = -1;
-    private int strLen;
-    private Paint mPaint;
-    
-///////////////////////////////////////////////////////////////////
-    
-    public TextFieldArea() 
-    {
-        text   = new StringBuffer();
-        strLen = 0;
-        mPaint = new Paint();
-        mPaint.setAntiAlias(true);
-    }
-
-///////////////////////////////////////////////////////////////////
-    
-    public void deleteLastChar() 
-    {
-        if (strLen > 0) 
-        {
-            text.deleteCharAt(strLen-1);
-            strLen--;
-        }
-    }
-
-///////////////////////////////////////////////////////////////////
-    
-    public void insertNewChar(char aChar) 
-    {
-        if ( ((maxSize > 0) && (text.length() < maxSize)) || (maxSize < 0)) 
-        { 
-            if (aChar != 0) 
-            {
-                text.insert(strLen, aChar);
-                strLen++;
-            }
-        }
-    }
-
-///////////////////////////////////////////////////////////////////
-    
-    public String getText() 
-    {
-        return text.toString();
-    }
-
-///////////////////////////////////////////////////////////////////
-    
-    public void setText(String newText) 
-    {
-        text   = null;
-        text   = new StringBuffer(newText);
-        strLen = newText.length();
-    }
-
-///////////////////////////////////////////////////////////////////
-   
-    public void clearTextField() 
-    {
-        text = null;
-        text = new StringBuffer();
-        strLen = 0;
-    }
-
-///////////////////////////////////////////////////////////////////
-    
-    public void paint( Canvas canvas, int left, int top, int right, int bottom) 
-    {
-    	int fontH = (int)((bottom-top)*0.7);
-    	
-        mPaint.setColor(textFieldBkgColor);
-        mPaint.setStyle(Style.FILL);
-        canvas.drawRect(left, top, right, bottom, mPaint);
-        mPaint.setColor(textFieldBorderColor);
-        mPaint.setStyle(Style.STROKE);
-        canvas.drawRect(left, top, right, bottom, mPaint);
-        mPaint.setColor(textFieldFontColor);
-        mPaint.setTextSize(fontH);   
-        
-        String t = text.toString();
-        
-        while( mPaint.measureText(t) > right-left-(bottom-top)/2 )
-          {
-          mPaint.setTextSize(--fontH);
-          }
-        
-        canvas.drawText( t, left+(bottom-top)/4, (bottom+top+fontH)/2-fontH/8, mPaint);
-    }
-
-////////////////////////////////////////////////////////////////////////////////
-    
-    public void setTextFieldBkgColor(int textFieldBkgColor) 
-    {
-        this.textFieldBkgColor = textFieldBkgColor;
-    }
-
-////////////////////////////////////////////////////////////////////////////////
-    
-    public void setTextFieldBorderColor(int textFieldBorderColor)
-    {
-        this.textFieldBorderColor = textFieldBorderColor;
-    }
-
-////////////////////////////////////////////////////////////////////////////////
- 
-    public void setTextFieldFontColor(int textFieldFontColor) 
-    {
-        this.textFieldFontColor = textFieldFontColor;
-    }
-
-////////////////////////////////////////////////////////////////////////////////
-
-    public int getMaxSize() 
-    {
-        return maxSize;
-    }
-
-////////////////////////////////////////////////////////////////////////////////
-  
-    public void setMaxSize(int maxSize) 
-    {
-        this.maxSize = maxSize;
-    }
-////////////////////////////////////////////////////////////////////////////////
-// end of TextFieldArea    
-}
diff --git a/distorted-sokoban/src/main/java/org/distorted/sokoban/TouchKeyboard.java b/distorted-sokoban/src/main/java/org/distorted/sokoban/TouchKeyboard.java
deleted file mode 100644
index 2dc71e3..0000000
--- a/distorted-sokoban/src/main/java/org/distorted/sokoban/TouchKeyboard.java
+++ /dev/null
@@ -1,459 +0,0 @@
-package org.distorted.sokoban;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Paint.Align;
-import android.graphics.Paint.Style;
-import android.graphics.drawable.NinePatchDrawable;
-
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.View;
-
-//import android.util.Log;
-
-////////////////////////////////////////////////////////////////////////////////
-
-public class TouchKeyboard extends View
-  {
-  //private static final String TAG_KEYBOARD = "RubikKeyboard";    
-       
-  private static final int  MARGIN = 2;
-  private static final int WMARGIN = 8;
-  private static final int BUTTON_NONE = -4;
-  private static final int BUTTON_OK   = -3;
-  private static final int BUTTON_BKSP = -2;
-  private static final int BUTTON_CAPS = -1;
- 
-  private int currPressed;
- 
-  private TextFieldArea textField;
-  private Paint mPaint;
-  private NinePatchDrawable keyBlue, keyRed, keyWhite;
-  private Bitmap ok, bksp, caps;
- 
-  private int bmpSize;
- 
-  char[][] lowerCaseL =
-      { {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'},
-        {'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r'},
-        {'s', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0'},
-        {'1', '2', '3', '4', '5', '6', '7', '8', '9'}
-      };
-   
-  char[][] upperCaseL =
-      { {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'},
-        {'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R'},
-        {'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0'},
-        {'1', '2', '3', '4', '5', '6', '7', '8', '9'}
-      };
- 
-  char[][] lowerCaseP =
-      { {'a', 'b', 'c', 'd', 'e', 'f'},
-        {'g', 'h', 'i', 'j', 'k', 'l'},
-        {'m', 'n', 'o', 'p', 'q', 'r'},
-        {'s', 't', 'u', 'v', 'w', 'x'},
-        {'y', 'z', '0', '1', '2', '3'},
-        {'4', '5', '6', '7', '8', '9'}
-      };
-
-  char[][] upperCaseP =
-      { {'A', 'B', 'C', 'D', 'E', 'F'},
-        {'G', 'H', 'I', 'J', 'K', 'L'},
-        {'M', 'N', 'O', 'P', 'Q', 'R'},
-        {'S', 'T', 'U', 'V', 'W', 'X'},
-        {'Y', 'Z', '0', '1', '2', '3'},
-        {'4', '5', '6', '7', '8', '9'}
-      };
- 
-  private int width, height;
- 
-  private int textFieldWidth;  
-  private int textFieldHeight;
-  private int textTopLeftX;  
-  private int textTopLeftY;
-  private int keyWidth, keyHeight;
-  private int sKeyWidth, sKeyHeight;
-     
-  private VirtualKeyboardListener invoker;
-   
-  private int charH;
-   
-  private int backgroundColor = 0xffffffff;
-  private int buttonsColor    = 0xff000000;
- 
-  private boolean uppercase;
-  private int minSize=0;
- 
-  public interface VirtualKeyboardListener
-    {    
-    void okPressed(String string);
-    }
- 
-////////////////////////////////////////////////////////////////////////////////
-
-  public TouchKeyboard(Context context, int scrWid, int scrHei)
-    {
-    super(context);
-    mPaint = new Paint();
-    mPaint.setAntiAlias(true);
-   
-    currPressed = BUTTON_NONE;
-    width = scrWid;
-    height= scrHei;
-   
-    Resources res = context.getResources();
-
-    keyBlue = (NinePatchDrawable)res.getDrawable(R.drawable.keys_blue);
-    keyRed  = (NinePatchDrawable)res.getDrawable(R.drawable.keys_red);
-    keyWhite= (NinePatchDrawable)res.getDrawable(R.drawable.keys_white);
-   
-    ok  = BitmapFactory.decodeResource(res, R.drawable.ok  );
-    bksp= BitmapFactory.decodeResource(res, R.drawable.bksp);
-    caps= BitmapFactory.decodeResource(res, R.drawable.caps);
-   
-    if( width<=height )
-      {
-      sKeyWidth = width/3;
-      sKeyHeight= width/5;
-      keyWidth  = width/6;
-      keyHeight = (height-2*sKeyHeight)/6;
-     
-      textFieldHeight = height-sKeyHeight-6*keyHeight;
-      textFieldWidth = width;
-      textTopLeftX = 0;
-      textTopLeftY = sKeyHeight;
-      }
-    else
-      {
-      sKeyWidth = sKeyHeight = keyHeight = height/5;
-      keyWidth = (width+1)/9;
-               
-      textFieldHeight= sKeyHeight;
-      textFieldWidth = width-3*sKeyWidth;
-      textTopLeftX = sKeyWidth;
-      textTopLeftY = 0;
-      }
- 
-    bmpSize = ok.getHeight();
-   
-    if( bmpSize> 0.8*sKeyHeight )
-      {
-      bmpSize = (int)(0.8*sKeyHeight);
-      ok   = Bitmap.createScaledBitmap(ok  , bmpSize, bmpSize, true);
-      bksp = Bitmap.createScaledBitmap(bksp, bmpSize, bmpSize, true);
-      caps = Bitmap.createScaledBitmap(caps, bmpSize, bmpSize, true);
-      }
-   
-    textField = new TextFieldArea();
-    charH= (int)(keyHeight*0.7);  
-    uppercase = true;
-    }
-
-////////////////////////////////////////////////////////////////////////////////
-
-  public void resetKeyBoard()
-    {
-    textField.clearTextField();
-    invalidate();
-    }
-
-////////////////////////////////////////////////////////////////////////////////
-
-  protected void onDraw(Canvas c)
-    {      
-    mPaint.setColor(backgroundColor);
-    mPaint.setStyle(Style.FILL);
-    mPaint.setTextSize(charH);
-    mPaint.setTextAlign(Align.CENTER);
-   
-    c.drawRect(0, 0, width, height, mPaint);
-   
-    mPaint.setColor(buttonsColor);
-   
-    textField.paint( c, textTopLeftX+1, textTopLeftY+MARGIN/2+1, textTopLeftX+textFieldWidth-1, textTopLeftY+textFieldHeight-MARGIN/2-1);
-   
-    if( currPressed==BUTTON_OK )
-      {
-      keyRed.setBounds( MARGIN/2, MARGIN/2, sKeyWidth-MARGIN/2, sKeyHeight-MARGIN/2);
-      keyRed.draw(c);
-      }
-    else
-      {
-      keyBlue.setBounds( MARGIN/2, MARGIN/2,sKeyWidth-MARGIN/2, sKeyHeight-MARGIN/2);
-      keyBlue.draw(c); 
-      }
-   
-    if( currPressed==BUTTON_BKSP )
-      {
-      keyRed.setBounds( width-2*sKeyWidth+MARGIN/2, MARGIN/2, width-sKeyWidth-MARGIN/2,sKeyHeight-MARGIN/2);
-      keyRed.draw(c);
-      }
-    else
-      {
-      keyBlue.setBounds(width-2*sKeyWidth+MARGIN/2, MARGIN/2, width-sKeyWidth-MARGIN/2,sKeyHeight-MARGIN/2);
-      keyBlue.draw(c); 
-      }
-   
-    if( currPressed==BUTTON_CAPS )
-      {
-      keyRed.setBounds( width-sKeyWidth+MARGIN/2, MARGIN/2, width-MARGIN/2,sKeyHeight-MARGIN/2);
-      keyRed.draw(c);
-      }
-    else
-      {
-      keyBlue.setBounds(width-sKeyWidth+MARGIN/2, MARGIN/2, width-MARGIN/2,sKeyHeight-MARGIN/2);
-      keyBlue.draw(c); 
-      }
-   
-    c.drawBitmap(ok  ,                   MARGIN/2+sKeyWidth/2-bmpSize/2, MARGIN/2+sKeyHeight/2-bmpSize/2, mPaint);
-    c.drawBitmap(bksp, width-2*sKeyWidth+MARGIN/2+sKeyWidth/2-bmpSize/2, MARGIN/2+sKeyHeight/2-bmpSize/2, mPaint);
-    c.drawBitmap(caps, width-  sKeyWidth+MARGIN/2+sKeyWidth/2-bmpSize/2, MARGIN/2+sKeyHeight/2-bmpSize/2, mPaint);
-   
-    int hei = (width<=height ? textFieldHeight+sKeyHeight : textFieldHeight);
-    int numCol = (width<=height ? lowerCaseP[0].length : lowerCaseL[0].length);
-    int numRow = (width<=height ? lowerCaseP.length : lowerCaseL.length);
-   
-    for(int j=0; j<numRow; j++)
-      {
-      for(int i=0; i<numCol; i++)
-        {  
-        if( currPressed==j*numCol+i )
-          {
-          keyRed.setBounds(  i*width/numCol+ MARGIN/2, hei+ MARGIN/2,
-                                     (i+1)*width/numCol- MARGIN/2, hei+ MARGIN/2 +keyHeight );
-          keyRed.draw(c);
-          keyWhite.setBounds(i*width/numCol+WMARGIN/2, hei+WMARGIN/2-keyHeight,
-                                 (i+1)*width/numCol-WMARGIN/2, hei-WMARGIN/2 );
-          keyWhite.draw(c);              
-         
-          if( width<=height )
-            c.drawText( uppercase? upperCaseP[j]:lowerCaseP[j], i,1,(float)(i*width/numCol+keyWidth/2),
-                        (float)(hei-keyHeight/2+charH*0.4), mPaint);
-          else
-                c.drawText( uppercase? upperCaseL[j]:lowerCaseL[j], i,1,(float)(i*width/numCol+keyWidth/2),
-                        (float)(hei-keyHeight/2+charH*0.4), mPaint);  
-          }
-        else
-          {
-          keyBlue.setBounds(  i*width/numCol+ MARGIN/2, hei+ MARGIN/2,
-                                  (i+1)*width/numCol- MARGIN/2, hei+ MARGIN/2 +keyHeight );
-          keyBlue.draw(c);
-          }
-       
-        if( width<=height )
-            c.drawText( uppercase? upperCaseP[j]:lowerCaseP[j], i,1,(float)(i*width/numCol+keyWidth/2),
-                        (float)(hei+keyHeight/2+charH*0.4), mPaint);
-        else
-            c.drawText( uppercase? upperCaseL[j]:lowerCaseL[j], i,1,(float)(i*width/numCol+keyWidth/2),
-                        (float)(hei+keyHeight/2+charH*0.4), mPaint);
-        }
-      hei+=keyHeight;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////
-
-  public boolean onTouchEvent(MotionEvent event)
-    {
-    switch(event.getAction())
-      {
-      case MotionEvent.ACTION_DOWN: buttonPressed( (int)event.getX(), (int)event.getY() );
-                                    break;
-      case MotionEvent.ACTION_UP  : buttonReleased();
-                                    break;
-      case MotionEvent.ACTION_MOVE: buttonPressed( (int)event.getX(), (int)event.getY() );
-                                    break;
-      }
-
-    invalidate();
-    return true;
-    }
-
-////////////////////////////////////////////////////////////////////////////////
-
-  private void buttonPressed(int x, int y)
-    {    
-    if( width<height )
-        {
-        if( y>=0 && y<sKeyHeight )
-              {
-                   if( x<=  sKeyWidth ) currPressed = BUTTON_OK;
-              else if( x<=2*sKeyWidth ) currPressed = BUTTON_BKSP;
-              else                      currPressed = BUTTON_CAPS;
-              }
-        else if( y>=sKeyHeight+textFieldHeight && y<=height )
-              {
-              currPressed = (int)((y-sKeyHeight-textFieldHeight)/keyHeight)*lowerCaseP[0].length + x/keyWidth;
-              }
-        }
-    else
-        {
-        if( y>=0 && y<textFieldHeight )
-              {
-                   if( x<=sKeyWidth )                               currPressed = BUTTON_OK;
-              else if( x>=width-2*sKeyWidth && x<=width-sKeyWidth ) currPressed = BUTTON_BKSP;
-              else if( x>=width- sKeyWidth && x<=width )            currPressed = BUTTON_CAPS;
-              else                                                  currPressed = BUTTON_NONE;
-              }
-        else if( y>=textFieldHeight && y<=height)
-              {
-              currPressed = (int)((y-textFieldHeight)/keyHeight)*lowerCaseL[0].length + x/keyWidth;
-              }
-        }
-    }
-
-////////////////////////////////////////////////////////////////////////////////
-
-  private void buttonReleased()
-    {
-         if( currPressed==BUTTON_OK   ) okButtonAction();
-    else if( currPressed==BUTTON_BKSP ) bkspButtonAction();  
-    else if( currPressed==BUTTON_CAPS ) capsButtonAction();  
-    else if( currPressed>=0           ) charAreaAction(currPressed);        
-               
-    currPressed = BUTTON_NONE;
-    }
-
-////////////////////////////////////////////////////////////////////////////////
- 
-  @Override public boolean onKeyDown(int keyCode, KeyEvent event)
-    {
-    if( currPressed!=BUTTON_NONE ) return false;
-   /*
-    switch( keyCode )
-      {
-      case Keypad.KEY_ALT        :
-      case Keypad.KEY_SHIFT_RIGHT:
-      case Keypad.KEY_SHIFT_LEFT : currPressed= BUTTON_CAPS; invalidate(); break;
-      case Keypad.KEY_ENTER      : currPressed= BUTTON_OK  ; invalidate(); break;
-      case Keypad.KEY_DELETE     :
-      case Keypad.KEY_BACKSPACE  : currPressed= BUTTON_BKSP; invalidate(); break;
-      }
-   
-    if( key>='A' && key<='Z' ) { currPressed = key-'A'          ; invalidate(); }
-    if( key>='a' && key<='z' ) { currPressed = key-'a'          ; invalidate(); }
-    if( key>='0' && key<='9' ) { currPressed = 'z'-'a'+1+key-'0'; invalidate(); }
-   */
-    return true;
-    }
-   
-////////////////////////////////////////////////////////////////////////////////
-   
-  @Override public boolean onKeyUp(int keyCode, KeyEvent event)
-    {
-    buttonReleased();
-    return true;
-    }
-   
-////////////////////////////////////////////////////////////////////////////////
-
-  private void capsButtonAction()
-    {
-    uppercase = !uppercase;
-    }
-
-////////////////////////////////////////////////////////////////////////////////
-
-  private void okButtonAction()
-    {
-    if ( invoker != null )
-      {
-      String text = textField.getText();
-
-      if( text.length()>= minSize )
-        invoker.okPressed(text);
-      }
-    }
-
-////////////////////////////////////////////////////////////////////////////////
-
-  private void bkspButtonAction()
-    {
-    textField.deleteLastChar();
-    }
-
-////////////////////////////////////////////////////////////////////////////////
-
-  private void charAreaAction(int pressed)
-    {    
-    int numCol = (width<height ? lowerCaseP[0].length:lowerCaseL[0].length);
-    int j = pressed/numCol;
-    int i = pressed%numCol;
-             
-    if( width<height )
-      textField.insertNewChar( uppercase ? upperCaseP[j][i]:lowerCaseP[j][i]);
-    else
-      textField.insertNewChar( uppercase ? upperCaseL[j][i]:lowerCaseL[j][i]);
-    }
- 
-////////////////////////////////////////////////////////////////////////////////
-
-  public void setText(String text)
-    {
-    textField.setText( (text==null||text.length()==0)?"":text);
-    invalidate();
-    }
-
-////////////////////////////////////////////////////////////////////////////////
- 
-  public void setTextFieldBkgColor(int color)
-    {
-    textField.setTextFieldBkgColor(color);  
-    }
-
-////////////////////////////////////////////////////////////////////////////////
- 
-  public void setTextFieldBorderColor(int color)
-    {
-    textField.setTextFieldBorderColor(color);  
-    }
-
-////////////////////////////////////////////////////////////////////////////////
- 
-  public void setTextFieldFontColor(int color)
-    {
-    textField.setTextFieldFontColor(color);  
-    }
- 
-////////////////////////////////////////////////////////////////////////////////
-
-  public int getBackgroundColor()
-    {
-    return backgroundColor;
-    }
- 
-////////////////////////////////////////////////////////////////////////////////
-
-  public void setBackgroundColor(int backgroundColor)
-    {
-    this.backgroundColor = backgroundColor;
-    }
-
-////////////////////////////////////////////////////////////////////////////////
-
-  public void setMaxSize(int maxSize)
-    {
-    textField.setMaxSize(maxSize);
-    }
-
-////////////////////////////////////////////////////////////////////////////////
-
-  public void setMinSize(int minSize)
-    {
-    this.minSize = minSize;
-    }
-
-////////////////////////////////////////////////////////////////////////////////
-
-  public void setVirtualKeyboardListener(VirtualKeyboardListener invoker)
-    {
-    this.invoker = invoker;
-    }
-////////////////////////////////////////////////////////////////////////////////
-// end of TouchKeyboard  
-}
\ No newline at end of file
