commit 86cbdab1ee3e47a902428de122347216fe956b03
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Sun Apr 12 13:06:13 2020 +0100

    Minor tweaks to the 'Prev' buton in Solving state;
    Relax requirements in the Manifest - now only 'android.hardware.faketouch' supporting device is required, i.e. something that supports basic point-click-drag-unclick. No need for full 'android.hardware.touchscreen' thing with its multitouch silliness.

diff --git a/build.gradle b/build.gradle
index 2b8014ba..3ee93006 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,7 +5,7 @@ android {
 
     defaultConfig {
         applicationId "org.distorted.magic"
-        minSdkVersion 25
+        minSdkVersion 21
         targetSdkVersion 27
         versionCode 1
         versionName "1.0"
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index 9bd31c3a..3759d7d4 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -2,6 +2,9 @@
     package="org.distorted.main">
 
     <uses-feature android:glEsVersion="0x00030001" android:required="true" />
+    <uses-feature android:name="android.hardware.faketouch" android:required="true" />
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+
     <uses-permission android:name="android.permission.INTERNET" />
 
     <application
diff --git a/src/main/java/org/distorted/scores/RubikScoresDownloader.java b/src/main/java/org/distorted/scores/RubikScoresDownloader.java
index 83785125..4b9a9930 100644
--- a/src/main/java/org/distorted/scores/RubikScoresDownloader.java
+++ b/src/main/java/org/distorted/scores/RubikScoresDownloader.java
@@ -28,6 +28,8 @@ import java.io.InputStream;
 import java.net.HttpURLConnection;
 import java.net.URL;
 import java.net.UnknownHostException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 
 import static org.distorted.objects.RubikObjectList.MAX_LEVEL;
 
@@ -98,30 +100,31 @@ public class RubikScoresDownloader implements Runnable
   private static RubikScoresDownloader mThis;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-// TODO
 
-  private static int computeHash(String name, String veri, String country, String lvllist,
-                                 String objlist, String timelist, int runnings)
+  private static String computeHash(String stringToHash, byte[] salt)
     {
-    int length, hash = 0;
-
-    length = name.length();
-    for(int i=0; i<length; i++) hash += i*name.charAt(i);
-    length = veri.length();
-    for(int i=0; i<length; i++) hash += i*veri.charAt(i);
-    length = country.length();
-    for(int i=0; i<length; i++) hash += i*country.charAt(i);
-    length = lvllist.length();
-    for(int i=0; i<length; i++) hash += i*lvllist.charAt(i);
-    length = objlist.length();
-    for(int i=0; i<length; i++) hash += i*objlist.charAt(i);
-    length = timelist.length();
-    for(int i=0; i<length; i++) hash += i*timelist.charAt(i);
-
-    hash *= runnings;
-    hash %= 541;
-
-    return hash<0 ? -hash: hash;
+    String generatedPassword;
+
+    try
+      {
+      MessageDigest md = MessageDigest.getInstance("MD5");
+      md.update(salt);
+      byte[] bytes = md.digest(stringToHash.getBytes());
+      StringBuilder sb = new StringBuilder();
+
+      for (byte aByte : bytes)
+        {
+        sb.append(Integer.toString((aByte & 0xff) + 0x100, 16).substring(1));
+        }
+
+      generatedPassword = sb.toString();
+      }
+    catch (NoSuchAlgorithmException e)
+      {
+      return "NoSuchAlgorithm";
+      }
+
+    return generatedPassword;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -330,14 +333,16 @@ public class RubikScoresDownloader implements Runnable
     String lvllist = scores.getUnsubmittedLevellist();
     String timlist = scores.getUnsubmittedTimelist();
     String country = scores.getCountry();
-    int hash = computeHash(name,veri,country,lvllist,objlist,timlist,numRuns);
+    long epoch = System.currentTimeMillis();
+    String salt = "cuboid";
 
-    String url="https://distorted.org/magic/cgi-bin/submit.cgi";
-    url += "?n="+name+"&v="+veri+"&r="+numRuns+"&p="+numPlay+"&i="+deviceID+"&e="+mVersion+"d";
-    url += "&o="+objlist+"&l="+lvllist+"&t="+timlist+"&c="+country+"&h="+hash;
-    url += "&oo="+RubikObjectList.getObjectList()+"&min=0&max="+MAX_LEVEL+"&lo="+MAX_PLACES;
+    String url1="https://distorted.org/magic/cgi-bin/submit.cgi";
+    String url2 = "n="+name+"&v="+veri+"&r="+numRuns+"&p="+numPlay+"&i="+deviceID+"&e="+mVersion+"d";
+    url2 += "&o="+objlist+"&l="+lvllist+"&t="+timlist+"&c="+country+"&f="+epoch;
+    url2 += "&oo="+RubikObjectList.getObjectList()+"&min=0&max="+MAX_LEVEL+"&lo="+MAX_PLACES;
+    url2 += "&h="+computeHash( url2, salt.getBytes() );
 
-    return url;
+    return url1 + "?" + url2;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/states/RubikStateSolving.java b/src/main/java/org/distorted/states/RubikStateSolving.java
index 0f019954..eef0f349 100644
--- a/src/main/java/org/distorted/states/RubikStateSolving.java
+++ b/src/main/java/org/distorted/states/RubikStateSolving.java
@@ -142,7 +142,7 @@ public class RubikStateSolving extends RubikStateAbstract implements RubikPostRe
     mPrevButton.setLayoutParams(params);
     mPrevButton.setPadding(padding,0,padding,0);
     mPrevButton.setImageResource(R.drawable.left);
-    mPrevButton.setEnabled(false);
+    mPrevButton.setVisibility(View.INVISIBLE);
 
     mPrevButton.setOnClickListener( new View.OnClickListener()
       {
@@ -180,7 +180,7 @@ public class RubikStateSolving extends RubikStateAbstract implements RubikPostRe
 
           if( numMoves==1 )
             {
-            mPrevButton.setEnabled(false);
+            mPrevButton.setVisibility(View.INVISIBLE);
             }
           }
         else
@@ -203,7 +203,7 @@ public class RubikStateSolving extends RubikStateAbstract implements RubikPostRe
 
     if( mMoves.size()==1 )
       {
-      mPrevButton.setEnabled(true);
+      mPrevButton.setVisibility(View.VISIBLE);
       }
     }
 
@@ -263,6 +263,7 @@ public class RubikStateSolving extends RubikStateAbstract implements RubikPostRe
           {
           mTime.setText(R.string.solved);
           mBack.setClickable(false);
+          mPrevButton.setVisibility(View.INVISIBLE);
           }
         });
 
