commit 4f9f99a22db17fde1a7f1cc63e85b94f629ceb59
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Tue Feb 18 23:08:42 2020 +0000

    Separate the network package; rename RubikSize to RubikObject.

diff --git a/src/main/java/org/distorted/dialog/RubikDialogScores.java b/src/main/java/org/distorted/dialog/RubikDialogScores.java
index fbdf174b..9ba39a2f 100644
--- a/src/main/java/org/distorted/dialog/RubikDialogScores.java
+++ b/src/main/java/org/distorted/dialog/RubikDialogScores.java
@@ -36,7 +36,7 @@ import android.widget.ImageView;
 import android.widget.TextView;
 
 import org.distorted.magic.R;
-import org.distorted.magic.RubikSize;
+import org.distorted.object.RubikObject;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -104,10 +104,10 @@ public class RubikDialogScores extends AppCompatDialogFragment
 
     viewPager.setCurrentItem(curTab);
 
-    for (int i = 0; i< RubikSize.LENGTH; i++)
+    for (int i = 0; i< RubikObject.LENGTH; i++)
       {
       ImageView imageView = new ImageView(act);
-      imageView.setImageResource(RubikSize.getSize(i).getIconID());
+      imageView.setImageResource(RubikObject.getObject(i).getIconID());
       TabLayout.Tab tab = tabLayout.getTabAt(i);
       if(tab!=null) tab.setCustomView(imageView);
       }
diff --git a/src/main/java/org/distorted/dialog/RubikDialogScoresPagerAdapter.java b/src/main/java/org/distorted/dialog/RubikDialogScoresPagerAdapter.java
index 10083572..6cee0bef 100644
--- a/src/main/java/org/distorted/dialog/RubikDialogScoresPagerAdapter.java
+++ b/src/main/java/org/distorted/dialog/RubikDialogScoresPagerAdapter.java
@@ -26,8 +26,8 @@ import android.support.v4.view.ViewPager;
 import android.view.View;
 import android.view.ViewGroup;
 
-import org.distorted.magic.RubikScoresDownloader;
-import org.distorted.magic.RubikSize;
+import org.distorted.network.RubikScoresDownloader;
+import org.distorted.object.RubikObject;
 import org.distorted.uistate.RubikStatePlay;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -48,7 +48,7 @@ class RubikDialogScoresPagerAdapter extends PagerAdapter implements RubikScoresD
 
     addPage(mViews[c],country[c],name[c],time[c]);
 
-    for(int i = 0; i< RubikSize.LENGTH; i++)
+    for(int i = 0; i< RubikObject.LENGTH; i++)
       {
       if( i==c ) continue;
 
@@ -65,7 +65,7 @@ class RubikDialogScoresPagerAdapter extends PagerAdapter implements RubikScoresD
       @Override
       public void run()
         {
-        for(int i=0; i<RubikSize.LENGTH; i++)
+        for(int i = 0; i< RubikObject.LENGTH; i++)
           {
           mViews[i].prepareView(mAct);
           }
@@ -121,7 +121,7 @@ class RubikDialogScoresPagerAdapter extends PagerAdapter implements RubikScoresD
       @Override
       public void run()
         {
-        for(int i=0; i<RubikSize.LENGTH; i++)
+        for(int i = 0; i< RubikObject.LENGTH; i++)
           {
           mViews[i].exception(exce);
           }
@@ -134,11 +134,11 @@ class RubikDialogScoresPagerAdapter extends PagerAdapter implements RubikScoresD
   RubikDialogScoresPagerAdapter(FragmentActivity act, ViewPager viewPager)
     {
     mAct = act;
-    mViews = new RubikDialogScoresView[RubikSize.LENGTH];
+    mViews = new RubikDialogScoresView[RubikObject.LENGTH];
     mViewPager = viewPager;
 
     viewPager.setAdapter(this);
-    viewPager.setOffscreenPageLimit( RubikSize.LENGTH-1 );
+    viewPager.setOffscreenPageLimit( RubikObject.LENGTH-1 );
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -152,7 +152,7 @@ class RubikDialogScoresPagerAdapter extends PagerAdapter implements RubikScoresD
 
     boolean allCreated = true;
 
-    for(int i=0; i<RubikSize.LENGTH; i++)
+    for(int i = 0; i< RubikObject.LENGTH; i++)
       {
       if( mViews[i]==null )
         {
@@ -183,7 +183,7 @@ class RubikDialogScoresPagerAdapter extends PagerAdapter implements RubikScoresD
   @Override
   public int getCount()
     {
-    return RubikSize.LENGTH;
+    return RubikObject.LENGTH;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/dialog/RubikDialogScoresView.java b/src/main/java/org/distorted/dialog/RubikDialogScoresView.java
index f740e312..98b30920 100644
--- a/src/main/java/org/distorted/dialog/RubikDialogScoresView.java
+++ b/src/main/java/org/distorted/dialog/RubikDialogScoresView.java
@@ -29,7 +29,7 @@ import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import org.distorted.magic.R;
-import org.distorted.magic.RubikScoresDownloader;
+import org.distorted.network.RubikScoresDownloader;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
diff --git a/src/main/java/org/distorted/magic/RubikActivity.java b/src/main/java/org/distorted/magic/RubikActivity.java
index 97643f19..1ca9f846 100644
--- a/src/main/java/org/distorted/magic/RubikActivity.java
+++ b/src/main/java/org/distorted/magic/RubikActivity.java
@@ -31,6 +31,8 @@ import org.distorted.dialog.RubikDialogSettings;
 import org.distorted.effect.BaseEffect;
 import org.distorted.library.main.DistortedLibrary;
 
+import org.distorted.network.RubikScoresDownloader;
+import org.distorted.object.RubikObject;
 import org.distorted.uistate.RubikState;
 import org.distorted.uistate.RubikStateAbstract;
 import org.distorted.uistate.RubikStatePlay;
@@ -88,9 +90,9 @@ public class RubikActivity extends AppCompatActivity implements View.OnClickList
       {
       int id = v.getId();
 
-      if( id>=0 && id<RubikSize.LENGTH )
+      if( id>=0 && id< RubikObject.LENGTH )
         {
-        int size = RubikSize.getSize(id).getCubeSize();
+        int size = RubikObject.getObject(id).getObjectSize();
 
         RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
         boolean success = view.getRenderer().createCube(size);
diff --git a/src/main/java/org/distorted/magic/RubikRenderer.java b/src/main/java/org/distorted/magic/RubikRenderer.java
index 900ce850..96a3a375 100644
--- a/src/main/java/org/distorted/magic/RubikRenderer.java
+++ b/src/main/java/org/distorted/magic/RubikRenderer.java
@@ -30,6 +30,7 @@ import org.distorted.library.main.DistortedTexture;
 import org.distorted.library.mesh.MeshFlat;
 import org.distorted.library.message.EffectListener;
 import org.distorted.object.RubikCube;
+import org.distorted.object.RubikObject;
 import org.distorted.uistate.RubikState;
 import org.distorted.uistate.RubikStatePlay;
 
@@ -87,7 +88,7 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
 
       RubikStatePlay play = (RubikStatePlay) RubikState.PLAY.getStateClass();
       int size = play.getButton();
-      mNextCubeSize = RubikSize.getSize(size).getCubeSize();
+      mNextCubeSize = RubikObject.getObject(size).getObjectSize();
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/magic/RubikScoresDownloader.java b/src/main/java/org/distorted/magic/RubikScoresDownloader.java
deleted file mode 100644
index f59ae37e..00000000
--- a/src/main/java/org/distorted/magic/RubikScoresDownloader.java
+++ /dev/null
@@ -1,293 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2019 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Magic Cube.                                                              //
-//                                                                                               //
-// Magic Cube 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.                                                           //
-//                                                                                               //
-// Magic Cube 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 Magic Cube.  If not, see <http://www.gnu.org/licenses/>.                           //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.magic;
-
-import android.content.res.Resources;
-
-import java.io.InputStream;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.net.UnknownHostException;
-
-import org.distorted.uistate.RubikStatePlay;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class RubikScoresDownloader implements Runnable
-  {
-  public interface Receiver
-    {
-    void receive(int[][][] country, String[][][] name, String[][][] time);
-    void exception(String exception);
-    }
-
-  public static final int MAX_PLACES = 12;
-
-  private static final int DOWNLOAD   = 0;
-  private static final int SUBMIT     = 1;
-  private static final int IDLE       = 2;
-
-  private static final String URL  ="http://koltunski.pl/rubik/cgi-bin";
-
-  private final String[] hex = {
-    "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
-    "%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f",
-    "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17",
-    "%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f",
-    "%20", "%21", "%22", "%23", "%24", "%25", "%26", "%27",
-    "%28", "%29", "%2a", "%2b", "%2c", "%2d", "%2e", "%2f",
-    "%30", "%31", "%32", "%33", "%34", "%35", "%36", "%37",
-    "%38", "%39", "%3a", "%3b", "%3c", "%3d", "%3e", "%3f",
-    "%40", "%41", "%42", "%43", "%44", "%45", "%46", "%47",
-    "%48", "%49", "%4a", "%4b", "%4c", "%4d", "%4e", "%4f",
-    "%50", "%51", "%52", "%53", "%54", "%55", "%56", "%57",
-    "%58", "%59", "%5a", "%5b", "%5c", "%5d", "%5e", "%5f",
-    "%60", "%61", "%62", "%63", "%64", "%65", "%66", "%67",
-    "%68", "%69", "%6a", "%6b", "%6c", "%6d", "%6e", "%6f",
-    "%70", "%71", "%72", "%73", "%74", "%75", "%76", "%77",
-    "%78", "%79", "%7a", "%7b", "%7c", "%7d", "%7e", "%7f",
-    "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
-    "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
-    "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
-    "%98", "%99", "%9a", "%9b", "%9c", "%9d", "%9e", "%9f",
-    "%a0", "%a1", "%a2", "%a3", "%a4", "%a5", "%a6", "%a7",
-    "%a8", "%a9", "%aa", "%ab", "%ac", "%ad", "%ae", "%af",
-    "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
-    "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf",
-    "%c0", "%c1", "%c2", "%c3", "%c4", "%c5", "%c6", "%c7",
-    "%c8", "%c9", "%ca", "%cb", "%cc", "%cd", "%ce", "%cf",
-    "%d0", "%d1", "%d2", "%d3", "%d4", "%d5", "%d6", "%d7",
-    "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
-    "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7",
-    "%e8", "%e9", "%ea", "%eb", "%ec", "%ed", "%ee", "%ef",
-    "%f0", "%f1", "%f2", "%f3", "%f4", "%f5", "%f6", "%f7",
-    "%f8", "%f9", "%fa", "%fb", "%fc", "%fd", "%fe", "%ff"
-    };
-
-  private static boolean mRunning = false;
-  private static int mMode = IDLE;
-  private static Receiver mReceiver;
-  private static Resources mResources;
-  private static String mPackageName;
-
-  private static String mScores = "";
-  private static int[][][] mCountry = new int   [RubikSize.LENGTH][RubikStatePlay.MAX_SCRAMBLE][MAX_PLACES];
-  private static String[][][] mName = new String[RubikSize.LENGTH][RubikStatePlay.MAX_SCRAMBLE][MAX_PLACES];
-  private static String[][][] mTime = new String[RubikSize.LENGTH][RubikStatePlay.MAX_SCRAMBLE][MAX_PLACES];
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void fillValues()
-    {
-    int begin=-1 ,end, len = mScores.length();
-
-    while( begin<len )
-      {
-      end = mScores.indexOf('\n', begin+1);
-      if( end<0 ) end = len;
-      fillRow(mScores.substring(begin+1,end));
-      begin = end;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void fillRow(String row)
-    {
-    int s1 = row.indexOf(' ');
-    int s2 = row.indexOf(' ',s1+1);
-    int s3 = row.indexOf(' ',s2+1);
-    int s4 = row.indexOf(' ',s3+1);
-    int s5 = row.indexOf(' ',s4+1);
-    int s6 = row.length();
-
-    if( s5>s4 && s4>s3 && s3>s2 && s2>s1 && s1>0 )
-      {
-      int size = Integer.valueOf( row.substring(0,s1) );
-
-      if( size>=0 && size<RubikSize.LENGTH )
-        {
-        int level      = Integer.valueOf( row.substring(s1+1,s2) );
-        int place      = Integer.valueOf( row.substring(s2+1,s3) );
-        String name    = row.substring(s3+1, s4);
-        int time       = Integer.valueOf( row.substring(s4+1,s5) );
-        String country = row.substring(s5+1, s6);
-        String realTime= String.valueOf(time/10.0f);
-
-        if(level>=0 && level<RubikStatePlay.MAX_SCRAMBLE && place>=0 && place<MAX_PLACES)
-          {
-          int resID = mResources.getIdentifier( country, "drawable", mPackageName);
-          mCountry[size][level][place] = resID!=0 ? resID:R.drawable.unk;
-          mName[size][level][place]    = name;
-          mTime[size][level][place]    = realTime;
-          }
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private String URLencode(String s)
-    {
-    StringBuilder sbuf = new StringBuilder();
-    int len = s.length();
-
-    for (int i = 0; i < len; i++)
-      {
-      int ch = s.charAt(i);
-
-           if ('A' <= ch && ch <= 'Z') sbuf.append((char)ch);
-      else if ('a' <= ch && ch <= 'z') sbuf.append((char)ch);
-      else if ('0' <= ch && ch <= '9') sbuf.append((char)ch);
-      else if (ch == ' '             ) sbuf.append('+');
-      else if (ch == '-' || ch == '_'
-            || ch == '.' || ch == '!'
-            || ch == '~' || ch == '*'
-            || ch == '\'' || ch == '('
-            || ch == ')'             ) sbuf.append((char)ch);
-      else if (ch <= 0x007f)           sbuf.append(hex[ch]);
-      else if (ch <= 0x07FF)
-        {
-        sbuf.append(hex[0xc0 | (ch >> 6)]);
-        sbuf.append(hex[0x80 | (ch & 0x3F)]);
-        }
-      else
-        {
-        sbuf.append(hex[0xe0 | (ch >> 12)]);
-        sbuf.append(hex[0x80 | ((ch >> 6) & 0x3F)]);
-        sbuf.append(hex[0x80 | (ch & 0x3F)]);
-        }
-      }
-
-    return sbuf.toString();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private boolean doDownload()
-    {
-    String veri = "distored";
-    String name = URLencode(veri);
-    int numRuns = 1;
-    String version = R.string.app_version+"d";
-    String message=URL+"/download.cgi?n="+name+"&r="+numRuns+"&e="+version;
-
-    try
-      {
-      java.net.URL connectURL = new URL(message);
-      HttpURLConnection conn = (HttpURLConnection)connectURL.openConnection();
-
-      conn.setDoInput(true);
-      conn.setDoOutput(true);
-      conn.setUseCaches(false);
-      conn.setRequestMethod("GET");
-      conn.connect();
-      conn.getOutputStream().flush();
-
-      try( InputStream is = conn.getInputStream() )
-        {
-        int ch;
-        StringBuilder sb = new StringBuilder();
-        while( ( ch = is.read() ) != -1 )
-          {
-          sb.append( (char)ch );
-          }
-        mScores = sb.toString();
-        }
-      catch( final Exception e)
-        {
-        mReceiver.exception("Failed to get an answer from the High Scores server");
-        return false;
-        }
-      }
-    catch( final UnknownHostException e )
-      {
-      mReceiver.exception("No access to Internet");
-      return false;
-      }
-    catch( final SecurityException e )
-      {
-      mReceiver.exception("Application not authorized to connect to the Internet");
-      return false;
-      }
-    catch( final Exception e )
-      {
-      mReceiver.exception(e.getMessage());
-      return false;
-      }
-
-    return true;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private boolean gottaDownload()
-    {
-    return ((mScores.length()==0) && !mRunning);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void onPause()
-    {
-    mRunning = false;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void download(Receiver receiver, Resources resources, String packageName)
-    {
-    mReceiver = receiver;
-    mResources= resources;
-    mPackageName = packageName;
-    mMode = DOWNLOAD;
-
-    Thread networkThrd = new Thread(this);
-    networkThrd.start();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void run()
-    {
-    boolean success=true;
-
-    try
-      {
-      if( gottaDownload() )
-        {
-        mRunning = true;
-        success = doDownload();
-        fillValues();
-        }
-      }
-    catch( Exception e )
-      {
-      mReceiver.exception("Exception downloading records: "+e.getMessage() );
-      }
-
-    mRunning = false;
-
-    if( success )
-      {
-      mReceiver.receive(mCountry, mName, mTime);
-      }
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/org/distorted/magic/RubikSize.java b/src/main/java/org/distorted/magic/RubikSize.java
deleted file mode 100644
index a374bfbd..00000000
--- a/src/main/java/org/distorted/magic/RubikSize.java
+++ /dev/null
@@ -1,76 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2020 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Magic Cube.                                                              //
-//                                                                                               //
-// Magic Cube 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.                                                           //
-//                                                                                               //
-// Magic Cube 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 Magic Cube.  If not, see <http://www.gnu.org/licenses/>.                           //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.magic;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public enum RubikSize
-  {
-  SIZE2 ( 2, R.drawable.button2 ),
-  SIZE3 ( 3, R.drawable.button3 ),
-  SIZE4 ( 4, R.drawable.button4 ),
-  SIZE5 ( 5, R.drawable.button5 ),
-  ;
-
-  public static final int LENGTH = values().length;
-  private final int mCubeSize, mIconID;
-  private static final RubikSize[] sizes;
-
-  static
-    {
-    int i = 0;
-    sizes = new RubikSize[LENGTH];
-
-    for(RubikSize size: RubikSize.values())
-      {
-      sizes[i] = size;
-      i++;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static RubikSize getSize(int ordinal)
-    {
-    return sizes[ordinal];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  RubikSize(int size, int iconID)
-    {
-    mCubeSize = size;
-    mIconID   = iconID;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int getIconID()
-    {
-    return mIconID;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int getCubeSize()
-    {
-    return mCubeSize;
-    }
-  }
diff --git a/src/main/java/org/distorted/network/RubikScoresDownloader.java b/src/main/java/org/distorted/network/RubikScoresDownloader.java
new file mode 100644
index 00000000..0d9be293
--- /dev/null
+++ b/src/main/java/org/distorted/network/RubikScoresDownloader.java
@@ -0,0 +1,298 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2019 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube 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.                                                           //
+//                                                                                               //
+// Magic Cube 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 Magic Cube.  If not, see <http://www.gnu.org/licenses/>.                           //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.network;
+
+import android.content.res.Resources;
+
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.UnknownHostException;
+
+import org.distorted.magic.R;
+import org.distorted.object.RubikObject;
+import org.distorted.uistate.RubikStatePlay;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikScoresDownloader implements Runnable
+  {
+  public interface Receiver
+    {
+    void receive(int[][][] country, String[][][] name, String[][][] time);
+    void exception(String exception);
+    }
+
+  public static final int MAX_PLACES = 12;
+
+  private static final int DOWNLOAD   = 0;
+  private static final int SUBMIT     = 1;
+  private static final int IDLE       = 2;
+
+  private static final String URL  ="http://koltunski.pl/rubik/cgi-bin";
+
+  private final String[] hex = {
+    "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
+    "%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f",
+    "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17",
+    "%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f",
+    "%20", "%21", "%22", "%23", "%24", "%25", "%26", "%27",
+    "%28", "%29", "%2a", "%2b", "%2c", "%2d", "%2e", "%2f",
+    "%30", "%31", "%32", "%33", "%34", "%35", "%36", "%37",
+    "%38", "%39", "%3a", "%3b", "%3c", "%3d", "%3e", "%3f",
+    "%40", "%41", "%42", "%43", "%44", "%45", "%46", "%47",
+    "%48", "%49", "%4a", "%4b", "%4c", "%4d", "%4e", "%4f",
+    "%50", "%51", "%52", "%53", "%54", "%55", "%56", "%57",
+    "%58", "%59", "%5a", "%5b", "%5c", "%5d", "%5e", "%5f",
+    "%60", "%61", "%62", "%63", "%64", "%65", "%66", "%67",
+    "%68", "%69", "%6a", "%6b", "%6c", "%6d", "%6e", "%6f",
+    "%70", "%71", "%72", "%73", "%74", "%75", "%76", "%77",
+    "%78", "%79", "%7a", "%7b", "%7c", "%7d", "%7e", "%7f",
+    "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
+    "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
+    "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
+    "%98", "%99", "%9a", "%9b", "%9c", "%9d", "%9e", "%9f",
+    "%a0", "%a1", "%a2", "%a3", "%a4", "%a5", "%a6", "%a7",
+    "%a8", "%a9", "%aa", "%ab", "%ac", "%ad", "%ae", "%af",
+    "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
+    "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf",
+    "%c0", "%c1", "%c2", "%c3", "%c4", "%c5", "%c6", "%c7",
+    "%c8", "%c9", "%ca", "%cb", "%cc", "%cd", "%ce", "%cf",
+    "%d0", "%d1", "%d2", "%d3", "%d4", "%d5", "%d6", "%d7",
+    "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
+    "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7",
+    "%e8", "%e9", "%ea", "%eb", "%ec", "%ed", "%ee", "%ef",
+    "%f0", "%f1", "%f2", "%f3", "%f4", "%f5", "%f6", "%f7",
+    "%f8", "%f9", "%fa", "%fb", "%fc", "%fd", "%fe", "%ff"
+    };
+
+  private static boolean mRunning = false;
+  private static int mMode = IDLE;
+  private static Receiver mReceiver;
+  private static Resources mResources;
+  private static String mPackageName;
+
+  private static String mScores = "";
+  private static int[][][] mCountry = new int   [RubikObject.LENGTH][RubikStatePlay.MAX_SCRAMBLE][MAX_PLACES];
+  private static String[][][] mName = new String[RubikObject.LENGTH][RubikStatePlay.MAX_SCRAMBLE][MAX_PLACES];
+  private static String[][][] mTime = new String[RubikObject.LENGTH][RubikStatePlay.MAX_SCRAMBLE][MAX_PLACES];
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void fillValues()
+    {
+    int begin=-1 ,end, len = mScores.length();
+
+    while( begin<len )
+      {
+      end = mScores.indexOf('\n', begin+1);
+      if( end<0 ) end = len;
+      fillRow(mScores.substring(begin+1,end));
+      begin = end;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void fillRow(String row)
+    {
+    int s1 = row.indexOf(' ');
+    int s2 = row.indexOf(' ',s1+1);
+    int s3 = row.indexOf(' ',s2+1);
+    int s4 = row.indexOf(' ',s3+1);
+    int s5 = row.indexOf(' ',s4+1);
+    int s6 = row.length();
+
+    if( s5>s4 && s4>s3 && s3>s2 && s2>s1 && s1>0 )
+      {
+      int size = Integer.valueOf( row.substring(0,s1) );
+
+      if( size>=0 && size< RubikObject.LENGTH )
+        {
+        int level      = Integer.valueOf( row.substring(s1+1,s2) );
+        int place      = Integer.valueOf( row.substring(s2+1,s3) );
+        String name    = row.substring(s3+1, s4);
+        int time       = Integer.valueOf( row.substring(s4+1,s5) );
+        String country = row.substring(s5+1, s6);
+        String realTime= String.valueOf(time/10.0f);
+
+        if(level>=0 && level<RubikStatePlay.MAX_SCRAMBLE && place>=0 && place<MAX_PLACES)
+          {
+          int resID = mResources.getIdentifier( country, "drawable", mPackageName);
+          mCountry[size][level][place] = resID!=0 ? resID: R.drawable.unk;
+          mName[size][level][place]    = name;
+          mTime[size][level][place]    = realTime;
+          }
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String URLencode(String s)
+    {
+    StringBuilder sbuf = new StringBuilder();
+    int len = s.length();
+
+    for (int i = 0; i < len; i++)
+      {
+      int ch = s.charAt(i);
+
+           if ('A' <= ch && ch <= 'Z') sbuf.append((char)ch);
+      else if ('a' <= ch && ch <= 'z') sbuf.append((char)ch);
+      else if ('0' <= ch && ch <= '9') sbuf.append((char)ch);
+      else if (ch == ' '             ) sbuf.append('+');
+      else if (ch == '-' || ch == '_'
+            || ch == '.' || ch == '!'
+            || ch == '~' || ch == '*'
+            || ch == '\'' || ch == '('
+            || ch == ')'             ) sbuf.append((char)ch);
+      else if (ch <= 0x007f)           sbuf.append(hex[ch]);
+      else if (ch <= 0x07FF)
+        {
+        sbuf.append(hex[0xc0 | (ch >> 6)]);
+        sbuf.append(hex[0x80 | (ch & 0x3F)]);
+        }
+      else
+        {
+        sbuf.append(hex[0xe0 | (ch >> 12)]);
+        sbuf.append(hex[0x80 | ((ch >> 6) & 0x3F)]);
+        sbuf.append(hex[0x80 | (ch & 0x3F)]);
+        }
+      }
+
+    return sbuf.toString();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean doDownload()
+    {
+    String veri = "distored";
+    String name = URLencode(veri);
+    int numRuns = 1;
+    String version = R.string.app_version+"d";
+    String message=URL+"/download.cgi?n="+name+"&r="+numRuns+"&e="+version;
+
+    try
+      {
+      java.net.URL connectURL = new URL(message);
+      HttpURLConnection conn = (HttpURLConnection)connectURL.openConnection();
+
+      conn.setDoInput(true);
+      conn.setDoOutput(true);
+      conn.setUseCaches(false);
+      conn.setRequestMethod("GET");
+      conn.connect();
+      conn.getOutputStream().flush();
+
+      try( InputStream is = conn.getInputStream() )
+        {
+        int ch;
+        StringBuilder sb = new StringBuilder();
+        while( ( ch = is.read() ) != -1 )
+          {
+          sb.append( (char)ch );
+          }
+        mScores = sb.toString();
+        }
+      catch( final Exception e)
+        {
+        mReceiver.exception("Failed to get an answer from the High Scores server");
+        return false;
+        }
+      }
+    catch( final UnknownHostException e )
+      {
+      mReceiver.exception("No access to Internet");
+      return false;
+      }
+    catch( final SecurityException e )
+      {
+      mReceiver.exception("Application not authorized to connect to the Internet");
+      return false;
+      }
+    catch( final Exception e )
+      {
+      mReceiver.exception(e.getMessage());
+      return false;
+      }
+
+    return true;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean gottaDownload()
+    {
+    return ((mScores.length()==0) && !mRunning);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @Override
+  public void run()
+    {
+    boolean success=true;
+
+    try
+      {
+      if( gottaDownload() )
+        {
+        mRunning = true;
+        success = doDownload();
+        fillValues();
+        }
+      }
+    catch( Exception e )
+      {
+      mReceiver.exception("Exception downloading records: "+e.getMessage() );
+      }
+
+    mRunning = false;
+
+    if( success )
+      {
+      mReceiver.receive(mCountry, mName, mTime);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void onPause()
+    {
+    mRunning = false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void download(Receiver receiver, Resources resources, String packageName)
+    {
+    mReceiver = receiver;
+    mResources= resources;
+    mPackageName = packageName;
+    mMode = DOWNLOAD;
+
+    Thread networkThrd = new Thread(this);
+    networkThrd.start();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/distorted/object/RubikObject.java b/src/main/java/org/distorted/object/RubikObject.java
new file mode 100644
index 00000000..d6a8366d
--- /dev/null
+++ b/src/main/java/org/distorted/object/RubikObject.java
@@ -0,0 +1,78 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2020 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube 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.                                                           //
+//                                                                                               //
+// Magic Cube 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 Magic Cube.  If not, see <http://www.gnu.org/licenses/>.                           //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.object;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+import org.distorted.magic.R;
+
+public enum RubikObject
+  {
+  CUBE2 ( 2, R.drawable.button2 ),
+  CUBE3 ( 3, R.drawable.button3 ),
+  CUBE4 ( 4, R.drawable.button4 ),
+  CUBE5 ( 5, R.drawable.button5 ),
+  ;
+
+  public static final int LENGTH = values().length;
+  private final int mObjectSize, mIconID;
+  private static final RubikObject[] objects;
+
+  static
+    {
+    int i = 0;
+    objects = new RubikObject[LENGTH];
+
+    for(RubikObject size: RubikObject.values())
+      {
+      objects[i] = size;
+      i++;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static RubikObject getObject(int ordinal)
+    {
+    return objects[ordinal];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  RubikObject(int size, int iconID)
+    {
+    mObjectSize = size;
+    mIconID     = iconID;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getIconID()
+    {
+    return mIconID;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getObjectSize()
+    {
+    return mObjectSize;
+    }
+  }
diff --git a/src/main/java/org/distorted/uistate/RubikStatePlay.java b/src/main/java/org/distorted/uistate/RubikStatePlay.java
index 273af1cd..0e911693 100644
--- a/src/main/java/org/distorted/uistate/RubikStatePlay.java
+++ b/src/main/java/org/distorted/uistate/RubikStatePlay.java
@@ -34,7 +34,7 @@ import android.widget.LinearLayout;
 import org.distorted.component.HorizontalNumberPicker;
 import org.distorted.magic.R;
 import org.distorted.magic.RubikActivity;
-import org.distorted.magic.RubikSize;
+import org.distorted.object.RubikObject;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -46,7 +46,7 @@ public class RubikStatePlay extends RubikStateAbstract
 
   private HorizontalNumberPicker mPicker;
   private int mPickerValue;
-  private int mButton = RubikSize.SIZE3.ordinal();
+  private int mButton = RubikObject.CUBE3.ordinal();
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -78,13 +78,13 @@ public class RubikStatePlay extends RubikStateAbstract
     int padding = (int)(3*scale + 0.5f);
     ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(size,size);
 
-    for(int i = 0; i< RubikSize.LENGTH; i++)
+    for(int i = 0; i< RubikObject.LENGTH; i++)
       {
       ImageButton button = new ImageButton(act);
       button.setLayoutParams(params);
       button.setId(i);
       button.setPadding(padding,0,padding,0);
-      int iconID = RubikSize.getSize(i).getIconID();
+      int iconID = RubikObject.getObject(i).getIconID();
       button.setImageResource(iconID);
       button.setOnClickListener(act);
       layoutBot.addView(button);
@@ -149,7 +149,7 @@ public class RubikStatePlay extends RubikStateAbstract
     {
     mButton = button;
 
-    for(int b=0; b<RubikSize.LENGTH; b++)
+    for(int b = 0; b< RubikObject.LENGTH; b++)
       {
       Drawable d = act.findViewById(b).getBackground();
 
