commit e06e1b7ec435215aa6f7f8ecb0a7a55b7464db40
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Thu Oct 1 23:29:36 2020 +0100

    - after some time using it, request an App review
    - some tweaking for the way the objects look (set internal_color to something slightly lighter than pure black, round the corners of the stickers better)
    - set internal_node ratio to 1.42, this way the objects never get cut

diff --git a/build.gradle b/build.gradle
index a2d06454..7aedf7d4 100644
--- a/build.gradle
+++ b/build.gradle
@@ -37,6 +37,7 @@ dependencies {
     implementation fileTree(dir: 'libs', include: ['*.jar'])
     implementation 'com.google.firebase:firebase-analytics:17.5.0'
     implementation 'com.google.firebase:firebase-crashlytics:17.2.2'
+    implementation 'com.google.android.play:core:1.8.2'
 
     api project(':distorted-library')
     implementation 'androidx.appcompat:appcompat:1.2.0'
diff --git a/src/main/java/org/distorted/main/RubikPreRender.java b/src/main/java/org/distorted/main/RubikPreRender.java
index ffa3caa5..719b762d 100644
--- a/src/main/java/org/distorted/main/RubikPreRender.java
+++ b/src/main/java/org/distorted/main/RubikPreRender.java
@@ -24,6 +24,14 @@ import android.content.SharedPreferences;
 import android.content.res.Resources;
 import android.os.Bundle;
 
+import androidx.annotation.NonNull;
+
+import com.google.android.play.core.review.ReviewInfo;
+import com.google.android.play.core.review.ReviewManager;
+import com.google.android.play.core.review.ReviewManagerFactory;
+import com.google.android.play.core.tasks.OnCompleteListener;
+import com.google.android.play.core.tasks.OnFailureListener;
+import com.google.android.play.core.tasks.Task;
 import com.google.firebase.analytics.FirebaseAnalytics;
 
 import org.distorted.dialogs.RubikDialogNewRecord;
@@ -58,7 +66,7 @@ public class RubikPreRender implements EffectListener
   private long[] mEffectID;
   private boolean mIsNewRecord;
   private long mNewRecord;
-  private int mScreenWidth, mScreenHeight;
+  private int mScreenWidth;
   private SharedPreferences mPreferences;
   private int[][] mNextMoves;
   private TwistyObject mOldObject, mNewObject;
@@ -92,7 +100,7 @@ public class RubikPreRender implements EffectListener
     mOldObject = null;
     mNewObject = null;
 
-    mScreenWidth = mScreenHeight = 0;
+    mScreenWidth = 0;
     mScrambleObjectNum = 0;
 
     mEffectID = new long[BaseEffect.Type.LENGTH];
@@ -352,11 +360,89 @@ public class RubikPreRender implements EffectListener
       }
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void requestReview()
+    {
+    final RubikScores scores = RubikScores.getInstance();
+    int numRuns   = scores.getNumRuns();
+    int numPlay   = scores.getNumPlays();
+    int numReview = scores.getNumReviews();
+
+    if( numRuns>=2 && numPlay>6 && numReview<1 )
+      {
+      final RubikActivity act = (RubikActivity)mView.getContext();
+      final ReviewManager manager = ReviewManagerFactory.create(act);
+      Task<ReviewInfo> request = manager.requestReviewFlow();
+
+      request.addOnCompleteListener(new OnCompleteListener<ReviewInfo>()
+        {
+        @Override
+        public void onComplete (@NonNull Task<ReviewInfo> task)
+          {
+          if (task.isSuccessful())
+            {
+            final String name = scores.getName();
+            scores.incrementNumReviews();
+            ReviewInfo reviewInfo = task.getResult();
+            Task<Void> flow = manager.launchReviewFlow(act, reviewInfo);
+
+            flow.addOnFailureListener(new OnFailureListener()
+              {
+              @Override
+              public void onFailure(Exception e)
+                {
+                analyticsReport(act,"Review Flow Failed", name);
+                }
+              });
+
+            flow.addOnCompleteListener(new OnCompleteListener<Void>()
+              {
+              @Override
+              public void onComplete(@NonNull Task<Void> task)
+                {
+                analyticsReport(act,"Review Flow Complete", name);
+                }
+              });
+            }
+          else
+            {
+            String name = scores.getName();
+            analyticsReport(act,"Request review flow not successful", name);
+            }
+          }
+        });
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void analyticsReport(RubikActivity act, String message, String name)
+    {
+    if( BuildConfig.DEBUG )
+       {
+       android.util.Log.d("pre", message);
+       android.util.Log.d("pre", name);
+       }
+    else
+      {
+      FirebaseAnalytics analytics = act.getAnalytics();
+
+      if( analytics!=null )
+        {
+        Bundle bundle = new Bundle();
+        bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, message);
+        bundle.putString(FirebaseAnalytics.Param.CHARACTER, name);
+        analytics.logEvent(FirebaseAnalytics.Event.CAMPAIGN_DETAILS, bundle);
+        }
+      }
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 //
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  void setScreenSize(int width, int height)
+  void setScreenSize(int width)
     {
     if( mNewObject!=null )
       {
@@ -364,7 +450,6 @@ public class RubikPreRender implements EffectListener
       mNewObject.recomputeScaleFactor(width);
       }
 
-    mScreenHeight = height;
     mScreenWidth  = width;
     }
 
@@ -587,6 +672,7 @@ public class RubikPreRender implements EffectListener
               bundle.putLong("time", mNewRecord );
 
               reportRecord();
+              requestReview();
 
               if( mIsNewRecord )
                 {
diff --git a/src/main/java/org/distorted/main/RubikRenderer.java b/src/main/java/org/distorted/main/RubikRenderer.java
index 50ba8449..519ef245 100644
--- a/src/main/java/org/distorted/main/RubikRenderer.java
+++ b/src/main/java/org/distorted/main/RubikRenderer.java
@@ -114,7 +114,7 @@ public class RubikRenderer implements GLSurfaceView.Renderer, DistortedLibrary.E
       {
       mScreen.resize(width,height);
       mView.setScreenSize(width,height);
-      mView.getPreRender().setScreenSize(width,height);
+      mView.getPreRender().setScreenSize(width);
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objects/TwistyDino.java b/src/main/java/org/distorted/objects/TwistyDino.java
index 94f0644f..632761a9 100644
--- a/src/main/java/org/distorted/objects/TwistyDino.java
+++ b/src/main/java/org/distorted/objects/TwistyDino.java
@@ -209,7 +209,7 @@ public abstract class TwistyDino extends TwistyObject
   void createFaceTexture(Canvas canvas, Paint paint, int face, int left)
     {
     float F = 0.5f;
-    float R = 0.035f;
+    float R = 0.032f;
     float S = 0.05f;
     float[] vertices = { -F,F/3, 0,-2*F/3, +F,F/3 };
 
diff --git a/src/main/java/org/distorted/objects/TwistyHelicopter.java b/src/main/java/org/distorted/objects/TwistyHelicopter.java
index 7f718121..6f919da7 100644
--- a/src/main/java/org/distorted/objects/TwistyHelicopter.java
+++ b/src/main/java/org/distorted/objects/TwistyHelicopter.java
@@ -200,7 +200,7 @@ public class TwistyHelicopter extends TwistyObject
 
   float getScreenRatio()
     {
-    return 1.5f;
+    return 1.6f;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -287,7 +287,7 @@ public class TwistyHelicopter extends TwistyObject
 
   void createFaceTexture(Canvas canvas, Paint paint, int face, int left)
     {
-    float R = 0.025f;
+    float R = 0.023f;
     float S = 0.035f;
     float E = 0.5f;
     float[] vertices = { -E+E/4,E/4, E/4,-E+E/4, E/4,E/4};
diff --git a/src/main/java/org/distorted/objects/TwistyObject.java b/src/main/java/org/distorted/objects/TwistyObject.java
index 62cb6352..4fcca030 100644
--- a/src/main/java/org/distorted/objects/TwistyObject.java
+++ b/src/main/java/org/distorted/objects/TwistyObject.java
@@ -64,14 +64,14 @@ public abstract class TwistyObject extends DistortedNode
   static final int COLOR_PINK   = 0xffff90ff;
   static final int COLOR_VIOLET = 0xff7700bb;
 
-  private static final float NODE_RATIO = 1.32f;
+  private static final float NODE_RATIO = 1.42f;
   private static final float MAX_SIZE_CHANGE = 1.4f;
   private static final float MIN_SIZE_CHANGE = 0.8f;
 
   private static boolean mCreateFromDMesh = true;
 
   private static final Static3D CENTER = new Static3D(0,0,0);
-  static final int INTERIOR_COLOR = 0xff000000;
+  static final int INTERIOR_COLOR = 0xff171717;
   private static final int POST_ROTATION_MILLISEC = 500;
   static final int TEXTURE_HEIGHT = 256;
 
diff --git a/src/main/java/org/distorted/objects/TwistySkewb.java b/src/main/java/org/distorted/objects/TwistySkewb.java
index 61b08ee2..287469fd 100644
--- a/src/main/java/org/distorted/objects/TwistySkewb.java
+++ b/src/main/java/org/distorted/objects/TwistySkewb.java
@@ -253,7 +253,7 @@ public class TwistySkewb extends TwistyObject
 
     if( face<COLORS )
       {
-      float R = 0.025f;
+      float R = 0.023f;
       float S = 0.035f;
       float E = 0.5f;
       float[] vertices = { -E+E/4,E/4, E/4,-E+E/4, E/4,E/4};
diff --git a/src/main/java/org/distorted/scores/RubikScores.java b/src/main/java/org/distorted/scores/RubikScores.java
index d2c1cf5b..320998f8 100644
--- a/src/main/java/org/distorted/scores/RubikScores.java
+++ b/src/main/java/org/distorted/scores/RubikScores.java
@@ -46,6 +46,7 @@ public class RubikScores
   private boolean mNameIsVerified;
   private int mNumRuns;
   private int mNumPlays;
+  private int mNumReviews;
   private int mDeviceID;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -68,9 +69,10 @@ public class RubikScores
 
     mNameIsVerified = false;
 
-    mNumPlays= -1;
-    mNumRuns = -1;
-    mDeviceID= -1;
+    mNumPlays   = -1;
+    mNumRuns    = -1;
+    mDeviceID   = -1;
+    mNumReviews =  0;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -94,144 +96,131 @@ public class RubikScores
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
+
+  synchronized void successfulSubmit()
+    {
+    mNameIsVerified = true;
+
+    for(int i=0; i<NUM_OBJECTS; i++)
+      for(int j=0; j<MAX_NUM_OBJECTS; j++)
+        for(int k=0; k<MAX_LEVEL; k++)
+          {
+          mSubmitted[i][j][k]=1;
+          }
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public static RubikScores getInstance()
+  int getDeviceID()
     {
-    if( mThis==null )
-      {
-      mThis = new RubikScores();
-      }
+    return mDeviceID;
+    }
 
-    return mThis;
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  boolean isVerified()
+    {
+    return mNameIsVerified;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public synchronized void savePreferences(SharedPreferences.Editor editor)
+  synchronized boolean thereAreUnsubmittedRecords()
     {
-    StringBuilder builder = new StringBuilder();
     ObjectList list;
-    int[] sizes;
     int length;
 
-    for(int level=0; level<MAX_LEVEL; level++)
+    for(int object=0; object<NUM_OBJECTS; object++)
       {
-      builder.setLength(0);
-
-      for(int object=0; object<NUM_OBJECTS; object++)
-        {
-        list = ObjectList.getObject(object);
-        sizes = list.getSizes();
-        length = sizes.length;
+      list = ObjectList.getObject(object);
+      length = list.getSizes().length;
 
-        for(int size=0; size<length; size++)
+      for(int size=0; size<length; size++)
+        for(int level=0; level<MAX_LEVEL; level++)
           {
-          builder.append(list.name());
-          builder.append("_");
-          builder.append(sizes[size]);
-          builder.append("=");
-          builder.append(mRecords[object][size][level]);
-          builder.append(",");
-          builder.append(mSubmitted[object][size][level]);
-          builder.append(" ");
+          if( mSubmitted[object][size][level]==0 && mRecords[object][size][level]<NO_RECORD )
+            {
+            return true;
+            }
           }
-        }
-
-      editor.putString("scores_record"+level, builder.toString());
       }
 
-    editor.putString("scores_name"  , mName  );
-    editor.putBoolean("scores_isVerified", mNameIsVerified);
-    editor.putInt("scores_numPlays", mNumPlays);
-    editor.putInt("scores_numRuns" , mNumRuns);
-    editor.putInt("scores_deviceid", mDeviceID);
+    return false;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public synchronized void restorePreferences(SharedPreferences preferences)
+  synchronized String getRecordList(String strObj, String strLvl, String strTim)
     {
-    String recordStr, subStr, nameStr, sizeStr, timeStr, submStr;
-    int start, end, equals, underscore, comma;
-    int object, sizeIndex, subm;
-    long time;
+    ObjectList list;
+    StringBuilder builderObj = new StringBuilder();
+    StringBuilder builderLvl = new StringBuilder();
+    StringBuilder builderTim = new StringBuilder();
+    boolean first = true;
+    int[] sizes;
+    int length;
 
-    for(int level=0; level<MAX_LEVEL; level++)
+    for(int object=0; object<NUM_OBJECTS; object++)
       {
-      start = end = 0;
-      recordStr = preferences.getString("scores_record"+level, "");
+      list = ObjectList.getObject(object);
+      sizes = list.getSizes();
+      length = sizes.length;
 
-      while( end!=-1 )
+      for(int size=0; size<length; size++)
         {
-        end = recordStr.indexOf(" ", start);
-
-        if( end==-1 ) subStr = recordStr.substring(start);
-        else          subStr = recordStr.substring(start,end);
-
-        start = end+1;
-
-        underscore = subStr.indexOf("_");
-        equals = subStr.indexOf("=");
-        comma = subStr.indexOf(",");
-
-        if( underscore>=0 && equals>=0 && comma>=0 )
+        for(int level=0; level<MAX_LEVEL; level++)
           {
-          nameStr = subStr.substring(0,underscore);
-          sizeStr = subStr.substring(underscore+1, equals);
-          timeStr = subStr.substring(equals+1,comma);
-          submStr = subStr.substring(comma+1);
-
-          object = ObjectList.getOrdinal(nameStr);
-
-          if( object>=0 && object< NUM_OBJECTS )
+          if( mSubmitted[object][size][level]==0 && mRecords[object][size][level]<NO_RECORD )
             {
-            sizeIndex = ObjectList.getSizeIndex(object,Integer.parseInt(sizeStr));
-            time = Long.parseLong(timeStr);
-            subm = Integer.parseInt(submStr);
-
-            if( sizeIndex>=0 && sizeIndex<MAX_NUM_OBJECTS && subm>=0 && subm<=1 )
+            if( !first )
               {
-              mRecords  [object][sizeIndex][level] = time;
-              mSubmitted[object][sizeIndex][level] = subm;
+              builderObj.append(',');
+              builderLvl.append(',');
+              builderTim.append(',');
               }
             else
               {
-              android.util.Log.e("scores", "error: size="+sizeIndex+" subm="+subm);
+              first=false;
               }
-            }
-          else
-            {
-            android.util.Log.e("scores", "error: object="+object);
+
+            builderObj.append(list.name());
+            builderObj.append("_");
+            builderObj.append(sizes[size]);
+            builderLvl.append(level);
+            builderTim.append(mRecords[object][size][level]);
             }
           }
         }
       }
 
-    mName           = preferences.getString("scores_name"  , "" );
-    mNameIsVerified = preferences.getBoolean("scores_isVerified", false);
-    mNumPlays       = preferences.getInt("scores_numPlays", 0);
-    mNumRuns        = preferences.getInt("scores_numRuns" , 0);
-    mDeviceID       = preferences.getInt("scores_deviceid",-1);
+    return strObj+builderObj.toString()+strLvl+builderLvl.toString()+strTim+builderTim.toString();
+    }
 
-    if( mDeviceID==-1 ) mDeviceID = privateGetDeviceID();
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Public API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public synchronized long getRecord(int object, int size, int level)
+    {
+    int maxsize = ObjectList.getObject(object).getSizes().length;
+
+    if( object>=0 && object<NUM_OBJECTS && size>=0 && size<maxsize && level>=0 && level<MAX_LEVEL )
+      {
+      return mRecords[object][size][level];
+      }
+
+    return -1;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public synchronized boolean setRecord(int object, int size, int level, long timeTaken)
+  public synchronized boolean isSubmitted(int object, int size, int level)
     {
     int maxsize = ObjectList.getObject(object).getSizes().length;
 
-    if( object>=0 && object<NUM_OBJECTS && size>=0 && size<maxsize && level>=1 && level<=MAX_LEVEL )
+    if( object>=0 && object<NUM_OBJECTS && size>=0 && size<maxsize && level>=0 && level<MAX_LEVEL )
       {
-      if( mRecords[object][size][level-1]> timeTaken )
-        {
-        mRecords  [object][size][level-1] = timeTaken;
-        mSubmitted[object][size][level-1] = 0;
-        return true;
-        }
+      return mSubmitted[object][size][level]==1;
       }
 
     return false;
@@ -239,60 +228,65 @@ public class RubikScores
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void incrementNumPlays()
+  public int getNumPlays()
     {
-    mNumPlays++;
+    return mNumPlays;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void incrementNumRuns()
+  public int getNumRuns()
     {
-    mNumRuns++;
+    return mNumRuns;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void setName(String newName)
+  public int getNumReviews()
     {
-    mName = newName;
+    return mNumReviews;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  synchronized void successfulSubmit()
+  public String getName()
     {
-    mNameIsVerified = true;
+    return mName;
+    }
 
-    for(int i=0; i<NUM_OBJECTS; i++)
-      for(int j=0; j<MAX_NUM_OBJECTS; j++)
-        for(int k=0; k<MAX_LEVEL; k++)
-          {
-          mSubmitted[i][j][k]=1;
-          }
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String getCountry()
+    {
+    return mCountry;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void setCountry(Context context)
+  public void incrementNumPlays()
     {
-    TelephonyManager tM =((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE));
+    mNumPlays++;
+    }
 
-    if( tM!=null )
-      {
-      mCountry = tM.getSimCountryIso();
+///////////////////////////////////////////////////////////////////////////////////////////////////
 
-      if( mCountry==null || mCountry.length()<=1 )
-        {
-        mCountry = tM.getNetworkCountryIso();
-        }
-      }
+  public void incrementNumRuns()
+    {
+    mNumRuns++;
+    }
 
-    // Special case: Dominicana. Its ISO-3166-alpha-2 country code is 'do' which we can't have here
-    // because we later on map this to a resource name (the flag) and 'do' is a reserved Java keyword
-    // and can't be a resource name.
+///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    if( mCountry.equals("do") ) mCountry = "dm";
+  public void incrementNumReviews()
+    {
+    mNumReviews++;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void setName(String newName)
+    {
+    mName = newName;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -320,144 +314,168 @@ public class RubikScores
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public synchronized long getRecord(int object, int size, int level)
+  public void setCountry(Context context)
     {
-    int maxsize = ObjectList.getObject(object).getSizes().length;
+    TelephonyManager tM =((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE));
 
-    if( object>=0 && object<NUM_OBJECTS && size>=0 && size<maxsize && level>=0 && level<MAX_LEVEL )
+    if( tM!=null )
       {
-      return mRecords[object][size][level];
+      mCountry = tM.getSimCountryIso();
+
+      if( mCountry==null || mCountry.length()<=1 )
+        {
+        mCountry = tM.getNetworkCountryIso();
+        }
       }
 
-    return -1;
+    // Special case: Dominicana. Its ISO-3166-alpha-2 country code is 'do' which we can't have here
+    // because we later on map this to a resource name (the flag) and 'do' is a reserved Java keyword
+    // and can't be a resource name.
+
+    if( mCountry.equals("do") ) mCountry = "dm";
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public synchronized boolean isSubmitted(int object, int size, int level)
+  public static RubikScores getInstance()
     {
-    int maxsize = ObjectList.getObject(object).getSizes().length;
-
-    if( object>=0 && object<NUM_OBJECTS && size>=0 && size<maxsize && level>=0 && level<MAX_LEVEL )
+    if( mThis==null )
       {
-      return mSubmitted[object][size][level]==1;
+      mThis = new RubikScores();
       }
 
-    return false;
+    return mThis;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  int getDeviceID()
+  public synchronized void savePreferences(SharedPreferences.Editor editor)
     {
-    return mDeviceID;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
+    StringBuilder builder = new StringBuilder();
+    ObjectList list;
+    int[] sizes;
+    int length;
 
-  boolean isVerified()
-    {
-    return mNameIsVerified;
-    }
+    for(int level=0; level<MAX_LEVEL; level++)
+      {
+      builder.setLength(0);
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
+      for(int object=0; object<NUM_OBJECTS; object++)
+        {
+        list = ObjectList.getObject(object);
+        sizes = list.getSizes();
+        length = sizes.length;
 
-  int getNumPlays()
-    {
-    return mNumPlays;
-    }
+        for(int size=0; size<length; size++)
+          {
+          builder.append(list.name());
+          builder.append("_");
+          builder.append(sizes[size]);
+          builder.append("=");
+          builder.append(mRecords[object][size][level]);
+          builder.append(",");
+          builder.append(mSubmitted[object][size][level]);
+          builder.append(" ");
+          }
+        }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
+      editor.putString("scores_record"+level, builder.toString());
+      }
 
-  int getNumRuns()
-    {
-    return mNumRuns;
+    editor.putString("scores_name"  , mName  );
+    editor.putBoolean("scores_isVerified", mNameIsVerified);
+    editor.putInt("scores_numPlays", mNumPlays);
+    editor.putInt("scores_numRuns" , mNumRuns);
+    editor.putInt("scores_deviceid", mDeviceID);
+    editor.putInt("scores_review"  , mNumReviews);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public String getName()
+  public synchronized void restorePreferences(SharedPreferences preferences)
     {
-    return mName;
-    }
+    String recordStr, subStr, nameStr, sizeStr, timeStr, submStr;
+    int start, end, equals, underscore, comma;
+    int object, sizeIndex, subm;
+    long time;
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
+    for(int level=0; level<MAX_LEVEL; level++)
+      {
+      start = end = 0;
+      recordStr = preferences.getString("scores_record"+level, "");
 
-  public String getCountry()
-    {
-    return mCountry;
-    }
+      while( end!=-1 )
+        {
+        end = recordStr.indexOf(" ", start);
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
+        if( end==-1 ) subStr = recordStr.substring(start);
+        else          subStr = recordStr.substring(start,end);
 
-  synchronized boolean thereAreUnsubmittedRecords()
-    {
-    ObjectList list;
-    int length;
+        start = end+1;
 
-    for(int object=0; object<NUM_OBJECTS; object++)
-      {
-      list = ObjectList.getObject(object);
-      length = list.getSizes().length;
+        underscore = subStr.indexOf("_");
+        equals = subStr.indexOf("=");
+        comma = subStr.indexOf(",");
 
-      for(int size=0; size<length; size++)
-        for(int level=0; level<MAX_LEVEL; level++)
+        if( underscore>=0 && equals>=0 && comma>=0 )
           {
-          if( mSubmitted[object][size][level]==0 && mRecords[object][size][level]<NO_RECORD )
+          nameStr = subStr.substring(0,underscore);
+          sizeStr = subStr.substring(underscore+1, equals);
+          timeStr = subStr.substring(equals+1,comma);
+          submStr = subStr.substring(comma+1);
+
+          object = ObjectList.getOrdinal(nameStr);
+
+          if( object>=0 && object< NUM_OBJECTS )
             {
-            return true;
+            sizeIndex = ObjectList.getSizeIndex(object,Integer.parseInt(sizeStr));
+            time = Long.parseLong(timeStr);
+            subm = Integer.parseInt(submStr);
+
+            if( sizeIndex>=0 && sizeIndex<MAX_NUM_OBJECTS && subm>=0 && subm<=1 )
+              {
+              mRecords  [object][sizeIndex][level] = time;
+              mSubmitted[object][sizeIndex][level] = subm;
+              }
+            else
+              {
+              android.util.Log.e("scores", "error: size="+sizeIndex+" subm="+subm);
+              }
+            }
+          else
+            {
+            android.util.Log.e("scores", "error: object="+object);
             }
           }
+        }
       }
 
-    return false;
+    mName           = preferences.getString("scores_name"  , "" );
+    mNameIsVerified = preferences.getBoolean("scores_isVerified", false);
+    mNumPlays       = preferences.getInt("scores_numPlays", 0);
+    mNumRuns        = preferences.getInt("scores_numRuns" , 0);
+    mDeviceID       = preferences.getInt("scores_deviceid",-1);
+    mNumReviews     = preferences.getInt("scores_review"  , 0);
+
+    if( mDeviceID==-1 ) mDeviceID = privateGetDeviceID();
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  synchronized String getRecordList(String strObj, String strLvl, String strTim)
+  public synchronized boolean setRecord(int object, int size, int level, long timeTaken)
     {
-    ObjectList list;
-    StringBuilder builderObj = new StringBuilder();
-    StringBuilder builderLvl = new StringBuilder();
-    StringBuilder builderTim = new StringBuilder();
-    boolean first = true;
-    int[] sizes;
-    int length;
+    int maxsize = ObjectList.getObject(object).getSizes().length;
 
-    for(int object=0; object<NUM_OBJECTS; object++)
+    if( object>=0 && object<NUM_OBJECTS && size>=0 && size<maxsize && level>=1 && level<=MAX_LEVEL )
       {
-      list = ObjectList.getObject(object);
-      sizes = list.getSizes();
-      length = sizes.length;
-
-      for(int size=0; size<length; size++)
+      if( mRecords[object][size][level-1]> timeTaken )
         {
-        for(int level=0; level<MAX_LEVEL; level++)
-          {
-          if( mSubmitted[object][size][level]==0 && mRecords[object][size][level]<NO_RECORD )
-            {
-            if( !first )
-              {
-              builderObj.append(',');
-              builderLvl.append(',');
-              builderTim.append(',');
-              }
-            else
-              {
-              first=false;
-              }
-
-            builderObj.append(list.name());
-            builderObj.append("_");
-            builderObj.append(sizes[size]);
-            builderLvl.append(level);
-            builderTim.append(mRecords[object][size][level]);
-            }
-          }
+        mRecords  [object][size][level-1] = timeTaken;
+        mSubmitted[object][size][level-1] = 0;
+        return true;
         }
       }
 
-    return strObj+builderObj.toString()+strLvl+builderLvl.toString()+strTim+builderTim.toString();
+    return false;
     }
   }
