commit a59f38d6589319d71777130c0e71f52f8fa4d157
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Wed Jul 13 13:07:45 2022 +0200

    Add Firebase Cloud Messaging and Firebase In-App Messaging

diff --git a/build.gradle b/build.gradle
index 93980d16..7bc83e94 100644
--- a/build.gradle
+++ b/build.gradle
@@ -37,10 +37,14 @@ dependencies {
     api project(':distorted-objectlib')
     api project(':distorted-flags')
 
-    implementation 'com.google.firebase:firebase-analytics:21.0.0'
-    implementation 'com.google.firebase:firebase-crashlytics:18.2.11'
+    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 'com.google.android.play:core:1.10.3'
     implementation 'androidx.appcompat:appcompat:1.4.2'
+    implementation "androidx.work:work-runtime:2.7.1"
     implementation 'com.google.android.material:material:1.6.1'
     implementation project(path: ':distorted-puzzle-jsons')
     implementation project(path: ':distorted-puzzle-dmesh')
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index fbc0a94b..975b4cdc 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -32,5 +32,13 @@
         <activity android:name="org.distorted.config.ConfigActivity" android:exported="false" android:screenOrientation="portrait"/>
         <activity android:name="org.distorted.bandaged.BandagedCreatorActivity" android:exported="false" android:screenOrientation="portrait"/>
         <activity android:name="org.distorted.bandaged.BandagedPlayActivity" android:exported="false" android:screenOrientation="portrait"/>
+
+        <service
+            android:name="org.distorted.messaging.RubikMessagingService"
+            android:exported="false">
+            <intent-filter>
+                <action android:name="com.google.firebase.MESSAGING_EVENT" />
+            </intent-filter>
+        </service>
     </application>
 </manifest>
diff --git a/src/main/java/org/distorted/external/RubikNetwork.java b/src/main/java/org/distorted/external/RubikNetwork.java
index f55ce1a5..2f9b34b6 100644
--- a/src/main/java/org/distorted/external/RubikNetwork.java
+++ b/src/main/java/org/distorted/external/RubikNetwork.java
@@ -116,6 +116,8 @@ public class RubikNetwork
     "%f8", "%f9", "%fa", "%fb", "%fc", "%fd", "%fe", "%ff"
     };
 
+  private static final String SERVER="https://distorted.org/magic/cgi-bin/";
+
   private static String[][][] mCountry;
   private static String[][][] mName;
   private static float[][][] mTime;
@@ -440,13 +442,24 @@ public class RubikNetwork
   private String constructSuspiciousURL(String suspURL)
     {
     RubikScores scores = RubikScores.getInstance();
-    int deviceID= scores.getDeviceID();
-    String suspicious = URLencode(suspURL);
+    int deviceID       = scores.getDeviceID();
+    String suspicious  = URLencode(suspURL);
 
-    String url="https://distorted.org/magic/cgi-bin/suspicious.cgi";
-    url += "?i="+deviceID+"&d="+suspicious;
+    return SERVER+"suspicious.cgi?i="+deviceID+"&d="+suspicious;
+    }
 
-    return url;
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String constructTokenURL(String token)
+    {
+    RubikScores scores = RubikScores.getInstance();
+    String name = URLencode(scores.getName());
+    int deviceID= scores.getDeviceID();
+    String country = scores.getCountry();
+    String version = mVersion==null ? "null" : mVersion;
+    String tkn = URLencode(token);
+
+    return SERVER+"token.cgi?n="+name+"&i="+deviceID+"&e="+version+"&c="+country+"&t="+tkn;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -466,7 +479,7 @@ public class RubikNetwork
     renderer = URLencode(renderer);
     version  = URLencode(version);
 
-    String url="https://distorted.org/magic/cgi-bin/debugs.cgi";
+    String url=SERVER+"debugs.cgi";
     url += "?n="+name+"&r="+numRuns+"&p="+numPlay+"&c="+country+"&e="+mVersion+"d";
     url += "&d="+renderer+"&v="+version+"&a="+objectAPI+"&b="+tutorialAPI;
 
@@ -483,10 +496,7 @@ public class RubikNetwork
     int numPlay = scores.getNumPlays();
     String country = scores.getCountry();
 
-    String url="https://distorted.org/magic/cgi-bin/download.cgi";
-    url += "?n="+name+"&r="+numRuns+"&p="+numPlay+"&c="+country+"&e="+mVersion;
-
-    return url;
+    return SERVER+"download.cgi?n="+name+"&r="+numRuns+"&p="+numPlay+"&c="+country+"&e="+mVersion;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -511,7 +521,7 @@ public class RubikNetwork
     renderer = parseRenderer(type,renderer);
     version  = parseVersion(type,version);
 
-    String url1="https://distorted.org/magic/cgi-bin/submit.cgi";
+    String url1=SERVER+"submit.cgi";
     String url2 = "n="+name+"&v="+veri+"&r="+numRuns+"&p="+numPlay+"&i="+deviceID+"&e="+mVersion;
     url2 += "&d="+renderer+"&s="+version+reclist+"&c="+country+"&f="+epoch;
     String hash = computeHash( url2, salt.getBytes() );
@@ -670,6 +680,32 @@ public class RubikNetwork
       }
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void tokenThread(String token)
+    {
+    String url = constructTokenURL(token);
+
+    try
+      {
+      java.net.URL connectURL = new URL(url);
+      HttpURLConnection conn = (HttpURLConnection)connectURL.openConnection();
+
+      conn.setDoInput(true);
+      conn.setDoOutput(true);
+      conn.setUseCaches(false);
+      conn.setRequestMethod("GET");
+      conn.connect();
+      conn.getOutputStream().flush();
+      conn.getInputStream();
+      conn.disconnect();
+      }
+    catch( final Exception e )
+      {
+      // ignore
+      }
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   private Bitmap downloadIcon(String url)
@@ -900,6 +936,23 @@ public class RubikNetwork
     thread.start();
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void token(final String token)
+    {
+    initializeStatics();
+
+    Thread thread = new Thread()
+      {
+      public void run()
+        {
+        tokenThread(token);
+        }
+      };
+
+    thread.start();
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // Yes it can happen that the second Updatee registers before we sent an update to the first one
 // and, as a result, the update never gets sent to the first one. This is not a problem (now, when
diff --git a/src/main/java/org/distorted/main/RubikActivity.java b/src/main/java/org/distorted/main/RubikActivity.java
index 5b8929cf..8ea906c0 100644
--- a/src/main/java/org/distorted/main/RubikActivity.java
+++ b/src/main/java/org/distorted/main/RubikActivity.java
@@ -41,11 +41,13 @@ import android.widget.LinearLayout;
 import androidx.appcompat.app.AppCompatActivity;
 
 import com.google.firebase.analytics.FirebaseAnalytics;
+import com.google.firebase.inappmessaging.FirebaseInAppMessaging;
 
 import org.distorted.config.ConfigActivity;
 import org.distorted.bandaged.BandagedCreatorActivity;
 import org.distorted.library.main.DistortedLibrary;
 
+import org.distorted.messaging.RubikInAppMessanging;
 import org.distorted.objectlib.main.ObjectControl;
 import org.distorted.objectlib.main.TwistyObject;
 import org.distorted.objectlib.effects.BaseEffect;
@@ -134,6 +136,9 @@ public class RubikActivity extends AppCompatActivity
       hideNavigationBar();
       cutoutHack();
       computeBarHeights();
+
+      RubikInAppMessanging listener = new RubikInAppMessanging();
+      FirebaseInAppMessaging.getInstance().addClickListener(listener);
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/messaging/RubikInAppMessanging.java b/src/main/java/org/distorted/messaging/RubikInAppMessanging.java
new file mode 100644
index 00000000..d519ea70
--- /dev/null
+++ b/src/main/java/org/distorted/messaging/RubikInAppMessanging.java
@@ -0,0 +1,51 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2022 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.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 RubikInAppMessanging 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/src/main/java/org/distorted/messaging/RubikMessagingService.java b/src/main/java/org/distorted/messaging/RubikMessagingService.java
new file mode 100644
index 00000000..bff8b070
--- /dev/null
+++ b/src/main/java/org/distorted/messaging/RubikMessagingService.java
@@ -0,0 +1,108 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2022 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.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;
+
+import org.distorted.external.RubikNetwork;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikMessagingService 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)
+    {
+    RubikNetwork network = RubikNetwork.getInstance();
+    network.token(token);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  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/src/main/java/org/distorted/solvers/cube3/SolverCoordCube.java b/src/main/java/org/distorted/solvers/cube3/SolverCoordCube.java
index 4fe2d630..461b51b9 100644
--- a/src/main/java/org/distorted/solvers/cube3/SolverCoordCube.java
+++ b/src/main/java/org/distorted/solvers/cube3/SolverCoordCube.java
@@ -36,8 +36,6 @@ class SolverCoordCube
 	static final short N_URtoUL   = 1320;  // 12!/(12-3)! permutation of UR,UF,UL edges
 	static final short N_UBtoDF   = 1320;  // 12!/(12-3)! permutation of UB,DR,DF edges
 	static final short N_URtoDF   = 20160; // 8!/(8-6)! permutation of UR,UF,UL,UB,DR,DF edges in phase2
-	static final int N_URFtoDLB   = 40320; // 8! permutations of the corners
-	static final int N_URtoBR     = 479001600;// 8! permutations of the corners
 	static final short N_MOVE     = 18;
 
 	// All coordinates are 0 for a solved cube except for UBtoDF, which is 114
