commit 9530f6b03839c42aef8d33a230b657f4ecd9064a
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Fri Mar 18 10:59:57 2022 +0100

    Beginnings of support for UI used to create any bandaged 3x3.

diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index 4be95db4..df4134e8 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -30,5 +30,7 @@
 
         <activity android:name="org.distorted.tutorials.TutorialActivity" android:exported="true" android:screenOrientation="portrait"/>
         <activity android:name="org.distorted.config.ConfigActivity" android:exported="true" android:screenOrientation="portrait"/>
+        <activity android:name="org.distorted.bandaged.BandagedCreatorActivity" android:exported="true" android:screenOrientation="portrait"/>
+        <activity android:name="org.distorted.bandaged.BandagedPlayActivity" android:exported="true" android:screenOrientation="portrait"/>
     </application>
 </manifest>
diff --git a/src/main/java/org/distorted/bandaged/BandagedCreatorActivity.java b/src/main/java/org/distorted/bandaged/BandagedCreatorActivity.java
new file mode 100644
index 00000000..c75e3607
--- /dev/null
+++ b/src/main/java/org/distorted/bandaged/BandagedCreatorActivity.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.bandaged;
+
+import android.os.Build;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.LinearLayout;
+import android.widget.ScrollView;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.google.firebase.analytics.FirebaseAnalytics;
+
+import org.distorted.dialogs.RubikDialogError;
+import org.distorted.library.main.DistortedLibrary;
+import org.distorted.main.R;
+import org.distorted.objectlib.main.ObjectControl;
+import org.distorted.objects.RubikObject;
+import org.distorted.objects.RubikObjectList;
+
+import java.io.InputStream;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class BandagedCreatorActivity extends AppCompatActivity
+{
+    private static final int ACTIVITY_NUMBER = 3;
+    private static final float RATIO_BAR   = 0.10f;
+    private static final float RATIO_SCROLL= 0.30f;
+
+    public static final float DIALOG_BUTTON_SIZE  = 0.06f;
+    public static final float MENU_BIG_TEXT_SIZE  = 0.05f;
+
+    public static final int FLAGS =  View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                                   | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                                   | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+                                   | View.SYSTEM_UI_FLAG_FULLSCREEN
+                                   | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+
+    private FirebaseAnalytics mFirebaseAnalytics;
+    private static int mScreenWidth, mScreenHeight;
+    private int mCurrentApiVersion;
+    private BandagedCreatorScreen mScreen;
+    private int mObjectOrdinal;
+    private int mHeightBar;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    protected void onCreate(Bundle savedState)
+      {
+      super.onCreate(savedState);
+      DistortedLibrary.onCreate(ACTIVITY_NUMBER);
+      setTheme(R.style.MaterialThemeNoActionBar);
+      setContentView(R.layout.bandaged);
+
+      mFirebaseAnalytics = FirebaseAnalytics.getInstance(this);
+
+      DisplayMetrics displaymetrics = new DisplayMetrics();
+      getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
+      mScreenWidth =displaymetrics.widthPixels;
+      mScreenHeight=displaymetrics.heightPixels;
+      mScreenHeight = (int)(1.07f*mScreenHeight); // add 7% for the upper bar
+                                                  // which is not yet hidden.
+                                                  // TODO: figure this out exactly.
+      hideNavigationBar();
+      cutoutHack();
+      computeHeights();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// this does not include possible insets
+
+    private void computeHeights()
+      {
+      int barHeight    = (int)(mScreenHeight*RATIO_BAR);
+      int scrollHeight = (int)(mScreenHeight*RATIO_SCROLL);
+      int viewHeight   = (int)(mScreenHeight*(1-RATIO_SCROLL));
+      mHeightBar = barHeight;
+
+      LinearLayout layout = findViewById(R.id.lowerBar);
+      ViewGroup.LayoutParams paramsL = layout.getLayoutParams();
+      paramsL.height = barHeight;
+      layout.setLayoutParams(paramsL);
+
+      ScrollView scroll = findViewById(R.id.bandagedCreatorScrollView);
+      ViewGroup.LayoutParams paramsS = scroll.getLayoutParams();
+      paramsS.height = scrollHeight;
+      scroll.setLayoutParams(paramsS);
+
+      BandagedCreatorView view = findViewById(R.id.bandagedCreatorView);
+      ViewGroup.LayoutParams paramsV = view.getLayoutParams();
+      paramsV.height = viewHeight;
+      view.setLayoutParams(paramsV);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void hideNavigationBar()
+      {
+      mCurrentApiVersion = Build.VERSION.SDK_INT;
+
+      if(mCurrentApiVersion >= Build.VERSION_CODES.KITKAT)
+        {
+        final View decorView = getWindow().getDecorView();
+
+        decorView.setSystemUiVisibility(FLAGS);
+
+        decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener()
+          {
+          @Override
+          public void onSystemUiVisibilityChange(int visibility)
+            {
+            if((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0)
+              {
+              decorView.setSystemUiVisibility(FLAGS);
+              }
+            }
+          });
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// do not avoid cutouts
+
+    private void cutoutHack()
+      {
+      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
+        {
+        getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus)
+      {
+      super.onWindowFocusChanged(hasFocus);
+
+      if(mCurrentApiVersion >= Build.VERSION_CODES.KITKAT && hasFocus)
+        {
+        getWindow().getDecorView().setSystemUiVisibility(FLAGS);
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    @Override
+    protected void onPause() 
+      {
+      super.onPause();
+      BandagedCreatorView view = findViewById(R.id.bandagedCreatorView);
+      view.onPause();
+      DistortedLibrary.onPause(ACTIVITY_NUMBER);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    @Override
+    protected void onResume() 
+      {
+      super.onResume();
+      DistortedLibrary.onResume(ACTIVITY_NUMBER);
+      BandagedCreatorView view = findViewById(R.id.bandagedCreatorView);
+      view.onResume();
+
+      if( mScreen==null ) mScreen = new BandagedCreatorScreen();
+      mScreen.onAttachedToWindow(this,mObjectOrdinal);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    @Override
+    protected void onDestroy() 
+      {
+      super.onDestroy();
+      DistortedLibrary.onDestroy(ACTIVITY_NUMBER);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    void OpenGLError()
+      {
+      RubikDialogError errDiag = new RubikDialogError();
+      errDiag.show(getSupportFragmentManager(), null);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void changeIfDifferent(RubikObject object,int ordinal,ObjectControl control)
+      {
+      if( object!=null )
+        {
+        int meshState          = object.getMeshState();
+        InputStream jsonStream = object.getObjectStream(this);
+        InputStream meshStream = object.getMeshStream(this);
+        String name            = object.getUpperName();
+
+        control.changeIfDifferent(ordinal,name,meshState,jsonStream,meshStream);
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void changeObject(int ordinal)
+      {
+      mObjectOrdinal = ordinal;
+      RubikObject object = RubikObjectList.getObject(ordinal);
+      BandagedCreatorView view = findViewById(R.id.bandagedCreatorView);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public FirebaseAnalytics getAnalytics()
+      {
+      return mFirebaseAnalytics;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public int getHeightBar()
+      {
+      return mHeightBar;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public int getScreenWidthInPixels()
+      {
+      return mScreenWidth;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public int getScreenHeightInPixels()
+      {
+      return mScreenHeight;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public static int getDrawableSize()
+      {
+      if( mScreenHeight<1000 )
+        {
+        return 0;
+        }
+      if( mScreenHeight<1600 )
+        {
+        return 1;
+        }
+      if( mScreenHeight<1900 )
+        {
+        return 2;
+        }
+
+      return 3;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public static int getDrawable(int small, int medium, int big, int huge)
+      {
+      int size = getDrawableSize();
+
+      switch(size)
+        {
+        case 0 : return small;
+        case 1 : return medium;
+        case 2 : return big;
+        default: return huge;
+        }
+      }
+}
diff --git a/src/main/java/org/distorted/bandaged/BandagedCreatorRenderer.java b/src/main/java/org/distorted/bandaged/BandagedCreatorRenderer.java
new file mode 100644
index 00000000..66f3497c
--- /dev/null
+++ b/src/main/java/org/distorted/bandaged/BandagedCreatorRenderer.java
@@ -0,0 +1,93 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.bandaged;
+
+import android.opengl.GLSurfaceView;
+
+import org.distorted.library.effect.EffectType;
+import org.distorted.library.effect.VertexEffectQuaternion;
+import org.distorted.library.effect.VertexEffectRotate;
+import org.distorted.library.main.DistortedLibrary;
+import org.distorted.library.main.DistortedScreen;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class BandagedCreatorRenderer implements GLSurfaceView.Renderer, DistortedLibrary.ExceptionListener
+{
+   private final BandagedCreatorView mView;
+   private final DistortedScreen mScreen;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   BandagedCreatorRenderer(BandagedCreatorView v)
+     {
+     final float BRIGHTNESS = 0.333f;
+
+     mView = v;
+     mScreen = new DistortedScreen();
+     mScreen.glClearColor(BRIGHTNESS, BRIGHTNESS, BRIGHTNESS, 1.0f);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   @Override
+   public void onDrawFrame(GL10 glUnused)
+     {
+     long time = System.currentTimeMillis();
+     mScreen.render(time);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   @Override
+   public void onSurfaceChanged(GL10 glUnused, int width, int height)
+      {
+      mScreen.resize(width,height);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   @Override
+   public void onSurfaceCreated(GL10 glUnused, EGLConfig config)
+      {
+      DistortedLibrary.setMax(EffectType.VERTEX,25);
+      VertexEffectRotate.enable();
+      VertexEffectQuaternion.enable();
+
+      DistortedLibrary.onSurfaceCreated(mView.getContext(),this,1);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   public void distortedException(Exception ex)
+     {
+     android.util.Log.e("CREATOR", "unexpected exception: "+ex.getMessage() );
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   DistortedScreen getScreen()
+     {
+     return mScreen;
+     }
+}
diff --git a/src/main/java/org/distorted/bandaged/BandagedCreatorScreen.java b/src/main/java/org/distorted/bandaged/BandagedCreatorScreen.java
new file mode 100644
index 00000000..2a0c1eb9
--- /dev/null
+++ b/src/main/java/org/distorted/bandaged/BandagedCreatorScreen.java
@@ -0,0 +1,134 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.bandaged;
+
+import android.view.View;
+import android.widget.LinearLayout;
+
+import org.distorted.helpers.TransparentImageButton;
+import org.distorted.main.R;
+import org.distorted.main.RubikActivity;
+import org.distorted.objectlib.main.ObjectControl;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class BandagedCreatorScreen
+{
+  private TransparentImageButton mBackButton, mResetButton, mDoneButton;
+  private int mObjectOrdinal;
+  private int mBarHeight;
+  private float mButtonSize;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public BandagedCreatorScreen()
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupBackButton(final BandagedCreatorActivity act)
+    {
+    int icon = RubikActivity.getDrawable(R.drawable.ui_small_smallback,R.drawable.ui_medium_smallback, R.drawable.ui_big_smallback, R.drawable.ui_huge_smallback);
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT);
+    mBackButton = new TransparentImageButton(act, icon, TransparentImageButton.GRAVITY_MIDDLE, params);
+
+    mBackButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        act.finish();
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupDoneButton(final BandagedCreatorActivity act)
+    {
+    int icon = RubikActivity.getDrawable(R.drawable.ui_small_smallback,R.drawable.ui_medium_smallback, R.drawable.ui_big_smallback, R.drawable.ui_huge_smallback);
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT);
+    mDoneButton = new TransparentImageButton(act, icon, TransparentImageButton.GRAVITY_MIDDLE, params);
+
+    mDoneButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupResetButton(final BandagedCreatorActivity act)
+    {
+    int icon = RubikActivity.getDrawable(R.drawable.ui_small_smallback,R.drawable.ui_medium_smallback, R.drawable.ui_big_smallback, R.drawable.ui_huge_smallback);
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT);
+    mResetButton = new TransparentImageButton(act, icon, TransparentImageButton.GRAVITY_MIDDLE, params);
+
+    mResetButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void onAttachedToWindow(final BandagedCreatorActivity act, final int objectOrdinal)
+    {
+    int width = act.getScreenWidthInPixels();
+    mBarHeight = act.getHeightBar();
+    mButtonSize = width*RubikActivity.BUTTON_TEXT_SIZE;
+
+    mObjectOrdinal = objectOrdinal;
+
+    LinearLayout.LayoutParams paramsL = new LinearLayout.LayoutParams(width/4, LinearLayout.LayoutParams.MATCH_PARENT);
+    LinearLayout.LayoutParams paramsM = new LinearLayout.LayoutParams(width/2, LinearLayout.LayoutParams.MATCH_PARENT);
+    LinearLayout.LayoutParams paramsR = new LinearLayout.LayoutParams(width/4, LinearLayout.LayoutParams.MATCH_PARENT);
+
+    LinearLayout layoutLeft = new LinearLayout(act);
+    layoutLeft.setLayoutParams(paramsL);
+    LinearLayout layoutMid  = new LinearLayout(act);
+    layoutMid.setLayoutParams(paramsM);
+    LinearLayout layoutRight= new LinearLayout(act);
+    layoutRight.setLayoutParams(paramsR);
+
+    setupBackButton(act);
+    layoutRight.addView(mBackButton);
+    setupDoneButton(act);
+    layoutMid.addView(mDoneButton);
+    setupResetButton(act);
+    layoutLeft.addView(mResetButton);
+
+    LinearLayout layout = act.findViewById(R.id.lowerBar);
+    layout.removeAllViews();
+    layout.addView(layoutLeft);
+    layout.addView(layoutMid);
+    layout.addView(layoutRight);
+    }
+}
diff --git a/src/main/java/org/distorted/bandaged/BandagedCreatorView.java b/src/main/java/org/distorted/bandaged/BandagedCreatorView.java
new file mode 100644
index 00000000..33db3dcd
--- /dev/null
+++ b/src/main/java/org/distorted/bandaged/BandagedCreatorView.java
@@ -0,0 +1,94 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.bandaged;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.ConfigurationInfo;
+import android.opengl.GLES30;
+import android.opengl.GLSurfaceView;
+import android.util.AttributeSet;
+
+import com.google.firebase.crashlytics.FirebaseCrashlytics;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class BandagedCreatorView extends GLSurfaceView
+{
+    private BandagedCreatorRenderer mRenderer;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public BandagedCreatorView(Context context, AttributeSet attrs)
+      {
+      super(context,attrs);
+
+      if(!isInEditMode())
+        {
+        BandagedCreatorActivity act = (BandagedCreatorActivity)context;
+        mRenderer = new BandagedCreatorRenderer(this);
+
+        final ActivityManager activityManager= (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+
+        try
+          {
+          final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
+          int esVersion = configurationInfo.reqGlEsVersion>>16;
+          setEGLContextClientVersion(esVersion);
+          setRenderer(mRenderer);
+          }
+        catch(Exception ex)
+          {
+          act.OpenGLError();
+
+          String shading = GLES30.glGetString(GLES30.GL_SHADING_LANGUAGE_VERSION);
+          String version = GLES30.glGetString(GLES30.GL_VERSION);
+          String vendor  = GLES30.glGetString(GLES30.GL_VENDOR);
+          String renderer= GLES30.glGetString(GLES30.GL_RENDERER);
+
+          FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
+          crashlytics.setCustomKey("GLSL Version"  , shading );
+          crashlytics.setCustomKey("GL version"    , version );
+          crashlytics.setCustomKey("GL Vendor "    , vendor  );
+          crashlytics.setCustomKey("GLSL renderer" , renderer);
+          crashlytics.recordException(ex);
+          }
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    public void onPause()
+      {
+      super.onPause();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    public void onResume()
+      {
+      super.onResume();
+      }
+}
+
diff --git a/src/main/java/org/distorted/bandaged/BandagedPlayActivity.java b/src/main/java/org/distorted/bandaged/BandagedPlayActivity.java
new file mode 100644
index 00000000..715971ed
--- /dev/null
+++ b/src/main/java/org/distorted/bandaged/BandagedPlayActivity.java
@@ -0,0 +1,313 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.bandaged;
+
+import android.os.Build;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.LinearLayout;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.google.firebase.analytics.FirebaseAnalytics;
+
+import org.distorted.config.ConfigSurfaceView;
+import org.distorted.dialogs.RubikDialogError;
+import org.distorted.library.main.DistortedLibrary;
+import org.distorted.main.R;
+import org.distorted.objectlib.main.ObjectControl;
+import org.distorted.objects.RubikObject;
+import org.distorted.objects.RubikObjectList;
+
+import java.io.InputStream;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class BandagedPlayActivity extends AppCompatActivity
+{
+    private static final int ACTIVITY_NUMBER = 4;
+    private static final float RATIO_BAR  = 0.10f;
+
+    public static final float DIALOG_BUTTON_SIZE  = 0.06f;
+    public static final float MENU_BIG_TEXT_SIZE  = 0.05f;
+
+    public static final int FLAGS =  View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                                   | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                                   | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+                                   | View.SYSTEM_UI_FLAG_FULLSCREEN
+                                   | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+
+    private FirebaseAnalytics mFirebaseAnalytics;
+    private static int mScreenWidth, mScreenHeight;
+    private int mCurrentApiVersion;
+    private BandagedPlayScreen mScreen;
+    private int mObjectOrdinal;
+    private int mHeightBar;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    protected void onCreate(Bundle savedState)
+      {
+      super.onCreate(savedState);
+      DistortedLibrary.onCreate(ACTIVITY_NUMBER);
+      setTheme(R.style.MaterialThemeNoActionBar);
+      setContentView(R.layout.bandaged_play);
+
+      Bundle b = getIntent().getExtras();
+
+      if(b != null) mObjectOrdinal = b.getInt("obj");
+
+      mFirebaseAnalytics = FirebaseAnalytics.getInstance(this);
+
+      DisplayMetrics displaymetrics = new DisplayMetrics();
+      getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
+      mScreenWidth =displaymetrics.widthPixels;
+      mScreenHeight=displaymetrics.heightPixels;
+      mScreenHeight = (int)(1.07f*mScreenHeight); // add 7% for the upper bar
+                                                  // which is not yet hidden.
+                                                  // TODO: figure this out exactly.
+      hideNavigationBar();
+      cutoutHack();
+      computeBarHeights();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// this does not include possible insets
+
+    private void computeBarHeights()
+      {
+      int barHeight = (int)(mScreenHeight*RATIO_BAR);
+      mHeightBar = barHeight;
+
+      LinearLayout layout = findViewById(R.id.lowerBar);
+      ViewGroup.LayoutParams params = layout.getLayoutParams();
+      params.height = barHeight;
+      layout.setLayoutParams(params);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void hideNavigationBar()
+      {
+      mCurrentApiVersion = Build.VERSION.SDK_INT;
+
+      if(mCurrentApiVersion >= Build.VERSION_CODES.KITKAT)
+        {
+        final View decorView = getWindow().getDecorView();
+
+        decorView.setSystemUiVisibility(FLAGS);
+
+        decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener()
+          {
+          @Override
+          public void onSystemUiVisibilityChange(int visibility)
+            {
+            if((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0)
+              {
+              decorView.setSystemUiVisibility(FLAGS);
+              }
+            }
+          });
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// do not avoid cutouts
+
+    private void cutoutHack()
+      {
+      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
+        {
+        getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus)
+      {
+      super.onWindowFocusChanged(hasFocus);
+
+      if(mCurrentApiVersion >= Build.VERSION_CODES.KITKAT && hasFocus)
+        {
+        getWindow().getDecorView().setSystemUiVisibility(FLAGS);
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    @Override
+    protected void onPause() 
+      {
+      super.onPause();
+      BandagedPlayView view = findViewById(R.id.bandagedPlayView);
+      view.onPause();
+      DistortedLibrary.onPause(ACTIVITY_NUMBER);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    @Override
+    protected void onResume() 
+      {
+      super.onResume();
+      DistortedLibrary.onResume(ACTIVITY_NUMBER);
+      BandagedPlayView view = findViewById(R.id.bandagedPlayView);
+      view.onResume();
+
+      if( mScreen==null ) mScreen = new BandagedPlayScreen();
+      mScreen.onAttachedToWindow(this,mObjectOrdinal);
+
+      if( mObjectOrdinal>=0 && mObjectOrdinal< RubikObjectList.getNumObjects() )
+        {
+        RubikObject object = RubikObjectList.getObject(mObjectOrdinal);
+        changeIfDifferent(object,mObjectOrdinal,view.getObjectControl());
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    @Override
+    protected void onDestroy() 
+      {
+      super.onDestroy();
+      DistortedLibrary.onDestroy(ACTIVITY_NUMBER);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    void OpenGLError()
+      {
+      RubikDialogError errDiag = new RubikDialogError();
+      errDiag.show(getSupportFragmentManager(), null);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void changeIfDifferent(RubikObject object,int ordinal,ObjectControl control)
+      {
+      if( object!=null )
+        {
+        int meshState          = object.getMeshState();
+        InputStream jsonStream = object.getObjectStream(this);
+        InputStream meshStream = object.getMeshStream(this);
+        String name            = object.getUpperName();
+
+        control.changeIfDifferent(ordinal,name,meshState,jsonStream,meshStream);
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void changeObject(int ordinal)
+      {
+      mObjectOrdinal = ordinal;
+      RubikObject object = RubikObjectList.getObject(ordinal);
+      BandagedPlayView view = findViewById(R.id.bandagedPlayView);
+      ObjectControl control = view.getObjectControl();
+      changeIfDifferent(object,ordinal,control);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public FirebaseAnalytics getAnalytics()
+      {
+      return mFirebaseAnalytics;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public int getHeightBar()
+      {
+      return mHeightBar;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public int getScreenWidthInPixels()
+      {
+      return mScreenWidth;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public int getScreenHeightInPixels()
+      {
+      return mScreenHeight;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public ObjectControl getControl()
+      {
+      BandagedPlayView view = findViewById(R.id.bandagedPlayView);
+      return view.getObjectControl();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public static int getDrawableSize()
+      {
+      if( mScreenHeight<1000 )
+        {
+        return 0;
+        }
+      if( mScreenHeight<1600 )
+        {
+        return 1;
+        }
+      if( mScreenHeight<1900 )
+        {
+        return 2;
+        }
+
+      return 3;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public static int getDrawable(int small, int medium, int big, int huge)
+      {
+      int size = getDrawableSize();
+
+      switch(size)
+        {
+        case 0 : return small;
+        case 1 : return medium;
+        case 2 : return big;
+        default: return huge;
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public boolean isVertical()
+      {
+      BandagedPlayView view = findViewById(R.id.bandagedPlayView);
+      return view.isVertical();
+      }
+}
diff --git a/src/main/java/org/distorted/bandaged/BandagedPlayLibInterface.java b/src/main/java/org/distorted/bandaged/BandagedPlayLibInterface.java
new file mode 100644
index 00000000..5b401cc5
--- /dev/null
+++ b/src/main/java/org/distorted/bandaged/BandagedPlayLibInterface.java
@@ -0,0 +1,139 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2021 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.bandaged;
+
+import com.google.firebase.crashlytics.FirebaseCrashlytics;
+
+import org.distorted.library.message.EffectMessageSender;
+import org.distorted.objectlib.BuildConfig;
+import org.distorted.objectlib.helpers.BlockController;
+import org.distorted.objectlib.helpers.ObjectLibInterface;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class BandagedPlayLibInterface implements ObjectLibInterface
+{
+  public void onWinEffectFinished(long startTime, long endTime, String debug, int scrambleNum) { }
+  public void onScrambleEffectFinished() { }
+  public void onBeginRotation() { }
+  public void onSolved() { }
+  public void onObjectCreated(long time) { }
+  public void onReplaceModeDown(int cubit, int face) { }
+  public void onReplaceModeUp() { }
+  public void onFinishRotation(int axis, int row, int angle) { }
+  public void failedToDrag() { }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void reportProblem(String problem, boolean reportException)
+    {
+    if( BuildConfig.DEBUG )
+      {
+      android.util.Log.e("interface", problem);
+      }
+    else
+      {
+      if( reportException )
+        {
+        Exception ex = new Exception(problem);
+        FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
+        crashlytics.setCustomKey("problem" , problem);
+        crashlytics.recordException(ex);
+        }
+      else
+        {
+        FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
+        crashlytics.log(problem);
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void reportUIProblem(int place, long pause, long resume, long time)
+    {
+    String error = "UI BLOCK "+place+" blocked for "+time;
+
+    if( BuildConfig.DEBUG )
+       {
+       android.util.Log.e("D", error);
+       }
+    else
+      {
+      Exception ex = new Exception(error);
+      FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
+      crashlytics.setCustomKey("pause" , pause );
+      crashlytics.setCustomKey("resume", resume );
+      crashlytics.recordException(ex);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void reportTouchProblem(int place, long pause, long resume, long time)
+    {
+    String error = "TOUCH BLOCK "+place+" blocked for "+time;
+
+    if( BuildConfig.DEBUG )
+       {
+       android.util.Log.e("D", error);
+       }
+    else
+      {
+      Exception ex = new Exception(error);
+      FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
+      crashlytics.setCustomKey("pause" , pause );
+      crashlytics.setCustomKey("resume", resume);
+      crashlytics.recordException(ex);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void reportThreadProblem(int place, long pause, long resume, long time)
+    {
+    String error = EffectMessageSender.reportState();
+
+    if( BuildConfig.DEBUG )
+       {
+       android.util.Log.e("D", error);
+       }
+    else
+      {
+      Exception ex = new Exception(error);
+      FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
+      crashlytics.setCustomKey("pause" , pause  );
+      crashlytics.setCustomKey("resume", resume );
+      crashlytics.recordException(ex);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void reportBlockProblem(int type, int place, long pause, long resume, long time)
+    {
+    switch(type)
+      {
+      case BlockController.TYPE_UI    : reportUIProblem(place,pause,resume,time); break;
+      case BlockController.TYPE_TOUCH : reportTouchProblem(place,pause,resume,time); break;
+      case BlockController.TYPE_THREAD: reportThreadProblem(place,pause,resume,time); break;
+      }
+    }
+}
diff --git a/src/main/java/org/distorted/bandaged/BandagedPlayRenderer.java b/src/main/java/org/distorted/bandaged/BandagedPlayRenderer.java
new file mode 100644
index 00000000..913b0d48
--- /dev/null
+++ b/src/main/java/org/distorted/bandaged/BandagedPlayRenderer.java
@@ -0,0 +1,97 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.bandaged;
+
+import android.opengl.GLSurfaceView;
+
+import org.distorted.library.effect.EffectType;
+import org.distorted.library.effect.VertexEffectQuaternion;
+import org.distorted.library.effect.VertexEffectRotate;
+import org.distorted.library.main.DistortedLibrary;
+import org.distorted.library.main.DistortedScreen;
+import org.distorted.objectlib.effects.BaseEffect;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class BandagedPlayRenderer implements GLSurfaceView.Renderer, DistortedLibrary.ExceptionListener
+{
+   private final BandagedPlayView mView;
+   private final DistortedScreen mScreen;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   BandagedPlayRenderer(BandagedPlayView v)
+     {
+     final float BRIGHTNESS = 0.333f;
+
+     mView = v;
+     mScreen = new DistortedScreen();
+     mScreen.glClearColor(BRIGHTNESS, BRIGHTNESS, BRIGHTNESS, 1.0f);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   @Override
+   public void onDrawFrame(GL10 glUnused)
+     {
+     long time = System.currentTimeMillis();
+     mView.getObjectControl().preRender();
+     mScreen.render(time);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   @Override
+   public void onSurfaceChanged(GL10 glUnused, int width, int height)
+      {
+      mScreen.resize(width,height);
+      mView.setScreenSize(width,height);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   @Override
+   public void onSurfaceCreated(GL10 glUnused, EGLConfig config)
+      {
+      DistortedLibrary.setMax(EffectType.VERTEX,25);
+      VertexEffectRotate.enable();
+      VertexEffectQuaternion.enable();
+      BaseEffect.Type.enableEffects();
+
+      DistortedLibrary.onSurfaceCreated(mView.getContext(),this,1);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   public void distortedException(Exception ex)
+     {
+     android.util.Log.e("BandagedPlay", "unexpected exception: "+ex.getMessage() );
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   DistortedScreen getScreen()
+     {
+     return mScreen;
+     }
+}
diff --git a/src/main/java/org/distorted/bandaged/BandagedPlayScreen.java b/src/main/java/org/distorted/bandaged/BandagedPlayScreen.java
new file mode 100644
index 00000000..5c03add3
--- /dev/null
+++ b/src/main/java/org/distorted/bandaged/BandagedPlayScreen.java
@@ -0,0 +1,124 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.bandaged;
+
+import android.view.View;
+import android.widget.LinearLayout;
+
+import org.distorted.helpers.LockController;
+import org.distorted.helpers.MovesController;
+import org.distorted.helpers.TransparentImageButton;
+import org.distorted.main.R;
+import org.distorted.main.RubikActivity;
+import org.distorted.objectlib.main.ObjectControl;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class BandagedPlayScreen
+{
+  private TransparentImageButton mBackButton, mScrambleButton, mSolveButton;
+  private final LockController mLockController;
+  protected MovesController mMovesController;
+  private int mObjectOrdinal;
+  private int mBarHeight;
+  private float mButtonSize;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public BandagedPlayScreen()
+    {
+    mLockController = new LockController();
+    mMovesController= new MovesController();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupBackButton(final BandagedPlayActivity act)
+    {
+    int icon = RubikActivity.getDrawable(R.drawable.ui_small_smallback,R.drawable.ui_medium_smallback, R.drawable.ui_big_smallback, R.drawable.ui_huge_smallback);
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT);
+    mBackButton = new TransparentImageButton(act, icon, TransparentImageButton.GRAVITY_MIDDLE, params);
+
+    mBackButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        ObjectControl control = act.getControl();
+        if( control!=null ) control.unblockEverything();
+        act.finish();
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void onAttachedToWindow(final BandagedPlayActivity act, final int objectOrdinal)
+    {
+    int width = act.getScreenWidthInPixels();
+    mBarHeight = act.getHeightBar();
+    mButtonSize = width*RubikActivity.BUTTON_TEXT_SIZE;
+
+    mObjectOrdinal = objectOrdinal;
+
+    LinearLayout.LayoutParams paramsL = new LinearLayout.LayoutParams(width/4, LinearLayout.LayoutParams.MATCH_PARENT);
+    LinearLayout.LayoutParams paramsM = new LinearLayout.LayoutParams(width/2, LinearLayout.LayoutParams.MATCH_PARENT);
+    LinearLayout.LayoutParams paramsR = new LinearLayout.LayoutParams(width/4, LinearLayout.LayoutParams.MATCH_PARENT);
+
+    LinearLayout layoutLeft = new LinearLayout(act);
+    layoutLeft.setLayoutParams(paramsL);
+    LinearLayout layoutMid  = new LinearLayout(act);
+    layoutMid.setLayoutParams(paramsM);
+    LinearLayout layoutRight= new LinearLayout(act);
+    layoutRight.setLayoutParams(paramsR);
+
+    setupBackButton(act);
+    ObjectControl control = act.getControl();
+    mMovesController.setupButton(act,control);
+    layoutLeft.addView(mMovesController.getButton());
+    mLockController.setupButton(act,control);
+    layoutMid.addView(mLockController.getButton());
+
+    layoutRight.addView(mBackButton);
+
+    LinearLayout layout = act.findViewById(R.id.lowerBar);
+    layout.removeAllViews();
+    layout.addView(layoutLeft);
+    layout.addView(layoutMid);
+    layout.addView(layoutRight);
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void setLockState(final RubikActivity act)
+    {
+    boolean locked = act.getControl().retLocked();
+    mLockController.setState(act,locked);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void reddenLock(final RubikActivity act)
+    {
+    ObjectControl control = act.getControl();
+    mLockController.reddenLock(act,control);
+    }
+}
diff --git a/src/main/java/org/distorted/bandaged/BandagedPlayView.java b/src/main/java/org/distorted/bandaged/BandagedPlayView.java
new file mode 100644
index 00000000..fb8e2a84
--- /dev/null
+++ b/src/main/java/org/distorted/bandaged/BandagedPlayView.java
@@ -0,0 +1,139 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.bandaged;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.ConfigurationInfo;
+import android.opengl.GLES30;
+import android.opengl.GLSurfaceView;
+import android.util.AttributeSet;
+
+import com.google.firebase.crashlytics.FirebaseCrashlytics;
+
+import org.distorted.objectlib.main.ObjectControl;
+import org.distorted.objectlib.main.TwistyObjectNode;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class BandagedPlayView extends GLSurfaceView
+{
+    private ObjectControl mObjectController;
+    private BandagedPlayRenderer mRenderer;
+    private int mScreenWidth, mScreenHeight;
+    private boolean mCreated;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    void setScreenSize(int width, int height)
+      {
+      mScreenWidth = width;
+      mScreenHeight= height;
+      mObjectController.setScreenSize(width,height);
+      mObjectController.setObjectScale(1.00f);
+
+      if( !mCreated )
+        {
+        mCreated = true;
+        mObjectController.createNode(width,height);
+        TwistyObjectNode objectNode = mObjectController.getNode();
+        mRenderer.getScreen().attach(objectNode);
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    boolean isVertical()
+      {
+      return mScreenHeight>mScreenWidth;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    ObjectControl getObjectControl()
+      {
+      return mObjectController;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public BandagedPlayView(Context context, AttributeSet attrs)
+      {
+      super(context,attrs);
+
+      mCreated = false;
+
+      if(!isInEditMode())
+        {
+        BandagedPlayActivity act = (BandagedPlayActivity)context;
+        BandagedPlayLibInterface ref = new BandagedPlayLibInterface();
+        mObjectController = new ObjectControl(act,ref);
+        mObjectController.setRotateOnCreation(true);
+        mRenderer = new BandagedPlayRenderer(this);
+
+        final ActivityManager activityManager= (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+
+        try
+          {
+          final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
+          int esVersion = configurationInfo.reqGlEsVersion>>16;
+          setEGLContextClientVersion(esVersion);
+          setRenderer(mRenderer);
+          }
+        catch(Exception ex)
+          {
+          act.OpenGLError();
+
+          String shading = GLES30.glGetString(GLES30.GL_SHADING_LANGUAGE_VERSION);
+          String version = GLES30.glGetString(GLES30.GL_VERSION);
+          String vendor  = GLES30.glGetString(GLES30.GL_VENDOR);
+          String renderer= GLES30.glGetString(GLES30.GL_RENDERER);
+
+          FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
+          crashlytics.setCustomKey("GLSL Version"  , shading );
+          crashlytics.setCustomKey("GL version"    , version );
+          crashlytics.setCustomKey("GL Vendor "    , vendor  );
+          crashlytics.setCustomKey("GLSL renderer" , renderer);
+          crashlytics.recordException(ex);
+          }
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    public void onPause()
+      {
+      super.onPause();
+      mObjectController.onPause();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    public void onResume()
+      {
+      super.onResume();
+      mObjectController.onResume();
+      }
+}
+
diff --git a/src/main/java/org/distorted/main/RubikActivity.java b/src/main/java/org/distorted/main/RubikActivity.java
index 29e498bc..bc4ec843 100644
--- a/src/main/java/org/distorted/main/RubikActivity.java
+++ b/src/main/java/org/distorted/main/RubikActivity.java
@@ -43,6 +43,7 @@ import androidx.appcompat.app.AppCompatActivity;
 import com.google.firebase.analytics.FirebaseAnalytics;
 
 import org.distorted.config.ConfigActivity;
+import org.distorted.bandaged.BandagedCreatorActivity;
 import org.distorted.library.main.DistortedLibrary;
 
 import org.distorted.objectlib.main.ObjectControl;
@@ -602,18 +603,26 @@ public class RubikActivity extends AppCompatActivity
 
     public void switchTutorial(String url, int objectOrdinal)
       {
-      Intent myIntent = new Intent(this, TutorialActivity.class);
-      myIntent.putExtra("url", url);
-      myIntent.putExtra("obj", objectOrdinal);
-      startActivity(myIntent);
+      Intent intent = new Intent(this, TutorialActivity.class);
+      intent.putExtra("url", url);
+      intent.putExtra("obj", objectOrdinal);
+      startActivity(intent);
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
     public void switchConfig(int objectOrdinal)
       {
-      Intent myIntent = new Intent(this, ConfigActivity.class);
-      myIntent.putExtra("obj", objectOrdinal);
-      startActivity(myIntent);
+      Intent intent = new Intent(this, ConfigActivity.class);
+      intent.putExtra("obj", objectOrdinal);
+      startActivity(intent);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void switchToBandagedCreator()
+      {
+      Intent intent = new Intent(this, BandagedCreatorActivity.class);
+      startActivity(intent);
       }
 }
diff --git a/src/main/java/org/distorted/screens/RubikScreenPlay.java b/src/main/java/org/distorted/screens/RubikScreenPlay.java
index c528111d..99b8a43e 100644
--- a/src/main/java/org/distorted/screens/RubikScreenPlay.java
+++ b/src/main/java/org/distorted/screens/RubikScreenPlay.java
@@ -70,6 +70,7 @@ public class RubikScreenPlay extends RubikScreenBase implements RubikNetwork.Upd
                                                R.string.patterns,
                                                R.string.solver,
                                                R.string.tutorials,
+                                               R.string.bandaged,
                                                R.string.about };
 
   private static final int NUM_BUTTONS = BUTTON_LABELS.length;
@@ -457,7 +458,9 @@ public class RubikScreenPlay extends RubikScreenBase implements RubikNetwork.Upd
       case 3: RubikDialogTutorial tDiag = new RubikDialogTutorial();
               tDiag.show( act.getSupportFragmentManager(), RubikDialogTutorial.getDialogTag() );
               break;
-      case 4: RubikDialogAbout aDiag = new RubikDialogAbout();
+      case 4: act.switchToBandagedCreator();
+              break;
+      case 5: RubikDialogAbout aDiag = new RubikDialogAbout();
               aDiag.show(act.getSupportFragmentManager(), null);
               break;
       }
diff --git a/src/main/res/layout/bandaged.xml b/src/main/res/layout/bandaged.xml
new file mode 100644
index 00000000..9e35aa57
--- /dev/null
+++ b/src/main/res/layout/bandaged.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout  xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/relativeLayout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <ScrollView
+        android:id="@+id/bandagedCreatorScrollView"
+        android:background="@color/black"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_alignParentStart="true"
+        android:layout_alignParentTop="true"/>
+
+    <org.distorted.bandaged.BandagedCreatorView
+        android:id="@+id/bandagedCreatorView"
+        android:layout_below="@+id/bandagedCreatorScrollView"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_alignParentStart="true"/>
+
+    <LinearLayout
+        android:id="@+id/lowerBar"
+        android:layout_alignParentBottom="true"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:orientation="horizontal"
+        android:background="@android:color/transparent">
+    </LinearLayout>
+
+</RelativeLayout>
diff --git a/src/main/res/layout/bandaged_play.xml b/src/main/res/layout/bandaged_play.xml
new file mode 100644
index 00000000..6199bf35
--- /dev/null
+++ b/src/main/res/layout/bandaged_play.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout  xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/relativeLayout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <org.distorted.bandaged.BandagedPlayView
+        android:id="@+id/bandagedPlayView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_alignParentStart="true"
+        android:layout_alignParentTop="true"/>
+
+    <LinearLayout
+        android:id="@+id/hiddenBar"
+        android:layout_alignParentTop="true"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:gravity="center"
+        android:orientation="horizontal"
+        android:background="@android:color/transparent">
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/upperBar"
+        android:layout_below="@id/hiddenBar"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:gravity="center"
+        android:orientation="horizontal"
+        android:background="@android:color/transparent">
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/lowerBar"
+        android:layout_alignParentBottom="true"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:orientation="horizontal"
+        android:background="@android:color/transparent">
+    </LinearLayout>
+
+</RelativeLayout>
diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml
index 80a955be..67f02c5f 100755
--- a/src/main/res/values-de/strings.xml
+++ b/src/main/res/values-de/strings.xml
@@ -44,6 +44,7 @@
     <string name="solver">3x3x3 Löser</string>
     <string name="tutorials">Lernprogrammen</string>
     <string name="about">Über die App</string>
+    <string name="bandaged">Bandaged 3x3</string>
     <string name="updates">Aktualisierungen</string>
     <string name="no_updates">Nicht gefunden</string>
     <string name="install">Installieren</string>
diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml
index 5280eb20..112eb51f 100755
--- a/src/main/res/values-es/strings.xml
+++ b/src/main/res/values-es/strings.xml
@@ -44,6 +44,7 @@
     <string name="solver">Solucionador</string>
     <string name="tutorials">Tutoriales</string>
     <string name="about">Acerca de</string>
+    <string name="bandaged">Bandaged 3x3</string>
     <string name="updates">Actualizaciones</string>
     <string name="no_updates">Extraviado</string>
     <string name="install">Instalar</string>
diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml
index d54586cd..e2ae6eed 100755
--- a/src/main/res/values-fr/strings.xml
+++ b/src/main/res/values-fr/strings.xml
@@ -44,6 +44,7 @@
     <string name="solver">Solutionneur</string>
     <string name="tutorials">Tutoriels</string>
     <string name="about">À propos de</string>
+    <string name="bandaged">Bandaged 3x3</string>
     <string name="updates">Mises à jour</string>
     <string name="no_updates">Pas trouvé</string>
     <string name="install">Installer</string>
diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml
index 0ca3dc3d..cce6a5d3 100755
--- a/src/main/res/values-ja/strings.xml
+++ b/src/main/res/values-ja/strings.xml
@@ -44,6 +44,7 @@
     <string name="solver">3x3x3 ソルバー</string>
     <string name="tutorials">チュートリアル</string>
     <string name="about">アプリについて</string>
+    <string name="bandaged">包帯3x3</string>
     <string name="updates">更新</string>
     <string name="no_updates">見つかりません</string>
     <string name="install">インストール</string>
diff --git a/src/main/res/values-ko/strings.xml b/src/main/res/values-ko/strings.xml
index 5c883322..25ef5985 100755
--- a/src/main/res/values-ko/strings.xml
+++ b/src/main/res/values-ko/strings.xml
@@ -44,6 +44,7 @@
     <string name="solver">3x3x3 해결사</string>
     <string name="tutorials">튜토리얼</string>
     <string name="about">정보</string>
+    <string name="bandaged">밴디지 큐브</string>
     <string name="updates">업데이트</string>
     <string name="no_updates">업데이트가 없습니다</string>
     <string name="install">설치</string>
diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml
index 5af0c645..6aa96cbc 100644
--- a/src/main/res/values-pl/strings.xml
+++ b/src/main/res/values-pl/strings.xml
@@ -44,6 +44,7 @@
     <string name="solver">Rozwiązywacz</string>
     <string name="tutorials">Tutoriale</string>
     <string name="about">O aplikacji</string>
+    <string name="bandaged">Bandaged 3x3</string>
     <string name="updates">Aktualizacje</string>
     <string name="no_updates">Nie znaleziono</string>
     <string name="install">Zainstaluj</string>
diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml
index 0edd941e..8b50b5f9 100755
--- a/src/main/res/values-ru/strings.xml
+++ b/src/main/res/values-ru/strings.xml
@@ -44,6 +44,7 @@
     <string name="solver">Решебник 3х3х3</string>
     <string name="tutorials">Учебники</string>
     <string name="about">О нас</string>
+    <string name="bandaged">Bandaged 3x3</string>
     <string name="updates">Обновления</string>
     <string name="no_updates">Не найдено</string>
     <string name="install">Установите</string>
diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml
index 8bc52440..ad0731d6 100644
--- a/src/main/res/values-zh-rCN/strings.xml
+++ b/src/main/res/values-zh-rCN/strings.xml
@@ -44,6 +44,7 @@
     <string name="solver">3x3x3求解器</string>
     <string name="tutorials">讲解</string>
     <string name="about">关于</string>
+    <string name="bandaged">捆绑魔方</string>
     <string name="updates">更新</string>
     <string name="no_updates">未找到更新</string>
     <string name="install">安装</string>
diff --git a/src/main/res/values-zh-rTW/strings.xml b/src/main/res/values-zh-rTW/strings.xml
index 0f00e9c1..85f65cf2 100644
--- a/src/main/res/values-zh-rTW/strings.xml
+++ b/src/main/res/values-zh-rTW/strings.xml
@@ -44,6 +44,7 @@
     <string name="solver">3x3x3求解器</string>
     <string name="tutorials">講解</string>
     <string name="about">關於</string>
+    <string name="bandaged">捆绑魔方</string>
     <string name="updates">更新</string>
     <string name="no_updates">未找到更新</string>
     <string name="install">安裝</string>
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index b17a5351..f63a5231 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -45,6 +45,7 @@
     <string name="solver">3x3x3 Solver</string>
     <string name="tutorials">Tutorials</string>
     <string name="about">About</string>
+    <string name="bandaged">Bandaged 3x3</string>
     <string name="updates">Updates</string>
     <string name="no_updates">No update found</string>
     <string name="install">Install</string>
