src/org/happysanta/gd/Menu/Views/MenuTextView.java
public class MenuTextView extends TextView {

	protected boolean isAttached = false;

	public MenuTextView(Context context) {
		super(context);
	}

	@Override
	protected void onAttachedToWindow() {
		super.onAttachedToWindow();
		isAttached = true;
	}

	@Override
	protected void onDetachedFromWindow() {
		super.onDetachedFromWindow();
		isAttached = false;
	}

	@Override
	public boolean isAttachedToWindow() {
		return isAttached;
	}

	public void setTextOnUiThread(final CharSequence sequence) {
		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				MenuTextView.super.setText(sequence);
			}
		});
	}

	@Override
	public void setTextSize(final float size) {
		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				MenuTextView.super.setTextSize(size);
			}
		});
	}

	@Override
	public void setTypeface(final Typeface typeface) {
		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				MenuTextView.super.setTypeface(typeface);
			}
		});
	}

	@Override
	public void setVisibility(final int visibility) {
		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				MenuTextView.super.setVisibility(visibility);
			}
		});
	}

}
src/org/happysanta/gd/Storage/Level.java
public class Level {

	private long id = 0;
	private String name;
	private String author;
	private int[] count;
	private int size = 0;
	private long addedTs = 0;
	private long installedTs = 0;
	private boolean _isDefault = false;
	private long apiId = 0;
	private int[] unlocked;
	private int selectedTrack = 0;
	private int selectedLevel = 0;
	private int selectedLeague = 0;
	private int unlockedLevels = 0;
	private int unlockedLeagues = 0;

	public Level() {
		count = new int[3];
		unlocked = new int[3];
	}

	public Level(long id, String name, String author, int countEasy, int countMedium, int countHard, int addedTs, int size, long apiId) {
		this(id, name, author, countEasy, countMedium, countHard, addedTs, size, apiId, 0, 0, 0);
	}

	public Level(long id, String name, String author, int countEasy, int countMedium, int countHard, int addedTs, int size, long apiId, int unlockedEasy, int unlockedMedium, int unlockedHard) {
		this.id = id;
		this.name = name;
		this.author = author;
		this.count = new int[]{
				countEasy, countMedium, countHard
		};
		this.addedTs = addedTs;
		this.size = size;
		this.apiId = apiId;
		this.unlocked = new int[]{
				unlockedEasy, unlockedMedium, unlockedHard
		};
	}

	public long getId() {
		return id;
	}

	public long getAnyId() {
		return id > 0 ? id : apiId;
	}

	public void setId(long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getAuthor() {
		return author;
	}

	public void setAuthor(String author) {
		this.author = author;
	}

	public int getCountEasy() {
		return this.count[0];
	}

	public int getCountMedium() {
		return this.count[1];
	}

	public int getCountHard() {
		return this.count[2];
	}

	public int getCount(int level) {
		return this.count[level];
	}

	public void setCountEasy(int count) {
		this.count[0] = count;
	}

	public void setCountMedium(int count) {
		this.count[1] = count;
	}

	public void setCountHard(int count) {
		this.count[2] = count;
	}

	public void setCount(int easy, int medium, int hard) {
		setCountEasy(easy);
		setCountMedium(medium);
		setCountHard(hard);
	}

	public long getAddedTs() {
		return addedTs;
	}

	public void setAddedTs(long ts) {
		addedTs = ts;
	}

	public long getInstalledTs() {
		return installedTs;
	}

	public void setInstalledTs(long ts) {
		installedTs = ts;
	}

	public void setIsDefault(boolean isDefault) {
		this._isDefault = isDefault;
	}

	public boolean isDefault() {
		return _isDefault;
	}

	public long getApiId() {
		return apiId;
	}

	public void setApiId(long apiId) {
		this.apiId = apiId;
	}

	public int getSize() {
		return size;
	}

	public void setSize(int size) {
		this.size = size;
	}

	public boolean isInstalled() {
		return id > 0;
	}

	public String getSizeKb() {
		return String.valueOf(Math.round((size / 1024f) * 100f) / 100f);
	}

	public String getShortAddedDate() {
		return getShortDate(addedTs);
	}

	public String getFullAddedDate() {
		return getFullDate(addedTs);
	}

	public String getShortInstalledDate() {
		return getShortDate(installedTs);
	}

	public String getFullInstalledDate() {
		return getFullDate(installedTs);
	}

	public int getUnlockedEasy() {
		return this.unlocked[0];
	}

	public int getUnlockedMedium() {
		return this.unlocked[1];
	}

	public int getUnlockedHard() {
		return this.unlocked[2];
	}

	public int getUnlocked(int level) {
		//if (level < 3)
		return unlocked[level];

		//logDebug("Level.getUnlocked: level = " + level + ", out of bounds");
		//return 0;
	}

	public int[] getUnlockedAll() {
		return unlocked;
	}

	public void setUnlockedEasy(int unlocked) {
		this.unlocked[0] = unlocked;
	}

	public void setUnlockedMedium(int unlocked) {
		this.unlocked[1] = unlocked;
	}

	public void setUnlockedHard(int unlocked) {
		this.unlocked[2] = unlocked;
	}

	public void setUnlocked(int easy, int medium, int hard) {
		setUnlockedEasy(easy);
		setUnlockedMedium(medium);
		setUnlockedHard(hard);
	}

	public void setUnlocked(int level, int value) {
		unlocked[level] = value;
	}

	public int getSelectedTrack() {
		return selectedTrack;
	}

	public int getSelectedLevel() {
		return selectedLevel;
	}

	public int getSelectedLeague() {
		return selectedLeague;
	}

	public void setSelectedTrack(int selectedTrack) {
		this.selectedTrack = selectedTrack;
	}

	public void setSelectedLevel(int selectedLevel) {
		this.selectedLevel = selectedLevel;
	}

	public void setSelectedLeague(int selectedLeague) {
		this.selectedLeague = selectedLeague;
	}

	public int getUnlockedLevels() {
		return unlockedLevels;
	}

	public int getUnlockedLeagues() {
		return unlockedLeagues;
	}

	public void setUnlockedLevels(int unlockedLevels) {
		this.unlockedLevels = unlockedLevels;
	}

	public void setUnlockedLeagues(int unlockedLeagues) {
		this.unlockedLeagues = unlockedLeagues;
	}

	public boolean isSettingsClear() {
		return unlockedLevels == 0
				&& unlocked[0] == 0
				&& unlocked[1] == 0
				&& unlocked[2] == 0;
	}

	@Override
	public String toString() {
		StringBuilder s = new StringBuilder();
		s.append("Storage.Level {");

		s.append("id: " + id + ", ");
		s.append("name: \"" + name + "\", ");
		s.append("author: \"" + author + "\", ");
		s.append("count: " + count[0] + "/" + count[1] + "/" + count[2] + ", ");
		s.append("added_ts: " + addedTs + ", ");
		s.append("installed_ts: " + installedTs + ", ");
		s.append("default: " + (_isDefault ? 1 : 0) + ", ");
		s.append("api_id: " + apiId + ", ");
		s.append("unlocked: " + unlocked[0] + "/" + unlocked[1] + "/" + unlocked[2] + ", ");
		s.append("selected_track: " + selectedTrack + ", ");
		s.append("selected_level: " + selectedLevel + ", ");
		s.append("selected_league: " + selectedLeague + ", ");
		s.append("unlocked_levels: " + unlockedLevels + ", ");
		s.append("unlocked_leagues: " + unlockedLeagues);

		s.append("}");
		return s.toString();
	}

	private static String getShortDate(long date) {
		return DateUtils.formatDateTime(getGDActivity(), date * 1000L, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH);
	}

	private static String getFullDate(long date) {
		return DateUtils.formatDateTime(getGDActivity(), date * 1000L, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR);
	}

}
src/org/happysanta/gd/Helpers.java
public static int getDp(int px) {
	return Math.round(px * Global.density);
}
src/org/happysanta/gd/Helpers.java
public static LevelsManager getLevelsManager() {
	return GDActivity.shared.levelsManager;
}
src/org/happysanta/gd/Menu/Views/LevelNameLeadingMarginSpan2.java
public class LevelNameLeadingMarginSpan2 implements LeadingMarginSpan.LeadingMarginSpan2 {

	private int margin;
	private int lines;

	public LevelNameLeadingMarginSpan2(int lines, int margin) {
		this.margin = margin;
		this.lines = lines;
	}

	@Override
	public int getLeadingMargin(boolean first) {
		if (first) {
			return margin;
		} else {
			return 0;
		}
	}

	@Override
	public void drawLeadingMargin(Canvas c, Paint p, int x, int dir,
								  int top, int baseline, int bottom, CharSequence text,
								  int start, int end, boolean first, Layout layout) {
	}

	@Override
	public int getLeadingMarginLineCount() {
		return lines;
	}

}
src/org/happysanta/gd/Helpers.java
public static Menu getGameMenu() {
	return GDActivity.shared.menu;
}
src/org/happysanta/gd/GDActivity.java
public class GDActivity extends Activity implements Runnable {

	public static GDActivity shared = null;
	public static final int MENU_TITLE_LAYOUT_TOP_PADDING = 25;
	public static final int MENU_TITLE_LAYOUT_BOTTOM_PADDING = 13;
	public static final int MENU_TITLE_LAYOUT_X_PADDING = 30;
	public static final int MENU_TITLE_FONT_SIZE = 30;
	public static final int GAME_MENU_BUTTON_LAYOUT_WIDTH = 40;
	public static final int GAME_MENU_BUTTON_LAYOUT_HEIGHT = 56;

	private static final long IMAGES_DELAY = 1000L;
	private static final long IMAGES_DELAY_DEBUG = 100L;

	public int m_longI = 0;

	private boolean wasPaused = false;
	private boolean wasStarted = false;
	private boolean wasDestroyed = false;
	private boolean restartingStarted = false;
	public boolean alive = false;
	public boolean m_cZ = true;
	private boolean menuShown = false;
	public boolean fullResetting = false;
	public boolean exiting = false;

	public GameView gameView = null;
	// public MenuView menuView = null;
	public Loader levelLoader;
	public Physics physEngine;
	public org.happysanta.gd.Menu.Menu menu;
	public boolean m_caseZ;
	public int m_nullI;
	public long m_forJ;
	// public long seconds;
	public long startedTime = 0;
	public long finishedTime = 0;
	public long pausedTime = 0;
	public long pausedTimeStarted = 0;
	public long m_byteJ;
	public boolean inited = false;
	public boolean m_ifZ;
	private Thread thread;
	private MenuImageView menuBtn;
	public MenuTitleLinearLayout titleLayout;
	public ObservableScrollView scrollView;
	private FrameLayout frame;
	private MenuLinearLayout menuLayout;
	private KeyboardController keyboardController;
	private boolean isNormalAndroid = true;
	private boolean buttonCoordsCalculated = false;
	public TextView menuTitleTextView;
	private boolean menuReady = false;
	private ArrayList<Command> commands = new ArrayList<Command>();
	private MenuLinearLayout keyboardLayout;
	private MenuTextView portedTextView;
	private int buttonHeight = 60;
	public LevelsManager levelsManager;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		shared = this;

		if (Helpers.isSDK10OrLower()) {
			isNormalAndroid = false;
		}

		final GDActivity self = this;
		Request request = API.getNotifications(Global.INSTALLED_FROM_APK, new ResponseHandler() {
			@Override
			public void onResponse(final Response apiResponse) {
				try {
					final NotificationsResponse response = new NotificationsResponse(apiResponse);
					if (!response.isEmpty()) {
						final Runnable onOk = new Runnable() {
							@Override
							public void run() {
								if (response.hasURL()) {
									String url = response.getURL();
									try {
										Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
										startActivity(browserIntent);
									} catch (Exception e) {
										e.printStackTrace();
									}
								}
							}
						};

						if (response.hasTwoButtons()) {
							AlertDialog.Builder alert = new AlertDialog.Builder(self)
									.setTitle(response.getTitle())
									.setMessage(response.getMessage())
									.setPositiveButton(response.getOKButton(), new DialogInterface.OnClickListener() {
										@Override
										public void onClick(DialogInterface dialog, int which) {
											onOk.run();
										}
									})
									.setNegativeButton(response.getCancelButton(), new DialogInterface.OnClickListener() {
										@Override
										public void onClick(DialogInterface dialog, int which) {
										}
									})
									.setOnCancelListener(new DialogInterface.OnCancelListener() {
										@Override
										public void onCancel(DialogInterface dialog) {

										}
									});
							alert.show();
						} else {
							AlertDialog alertDialog = new AlertDialog.Builder(self)
									.setTitle(response.getTitle())
									.setMessage(response.getMessage())
									.setPositiveButton(response.getOKButton(), new DialogInterface.OnClickListener() {
										@Override
										public void onClick(DialogInterface dialog, int which) {
											onOk.run();
										}
									})
									.setOnCancelListener(new DialogInterface.OnCancelListener() {
										@Override
										public void onCancel(DialogInterface dialog) {
										}
									})
									.create();
							alertDialog.show();
						}
					}
				} catch (Exception e) {
					// e.printStackTrace();
				}
			}

			@Override
			public void onError(APIException error) {

			}
		});

		if (true) {
			gameView = new GameView(this);

			getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);

			scrollView = new ObservableScrollView(this);
			scrollView.setBackgroundColor(0x00ffffff);
			scrollView.setFillViewport(true);
			scrollView.setOnScrollListener(new ObservableScrollView.OnScrollListener() {
				@Override
				public void onScroll(ObservableScrollView scrollView, int x, int y, int oldx, int oldy) {
					if (isMenuShown() && menu != null && menu.currentMenu != null) {
						int h = scrollView.getChildAt(0).getHeight() - scrollView.getHeight();
						double p = 100.0 * y / h;
						if (p > 100f)
							p = 100f;

						menu.currentMenu.onScroll(p);
					}
				}
			});
			scrollView.setVisibility(View.GONE);

			frame = new FrameLayout(this);
			frame.setBackgroundColor(0xffffffff);

			titleLayout = new MenuTitleLinearLayout(this);
			titleLayout.setBackgroundColor(0x00ffffff);
			titleLayout.setGravity(Gravity.TOP);
			titleLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
			titleLayout.setPadding(Helpers.getDp(MENU_TITLE_LAYOUT_X_PADDING), Helpers.getDp(MENU_TITLE_LAYOUT_TOP_PADDING), Helpers.getDp(MENU_TITLE_LAYOUT_X_PADDING), Helpers.getDp(MENU_TITLE_LAYOUT_BOTTOM_PADDING));

			menuTitleTextView = new TextView(this);
			menuTitleTextView.setText(getString(R.string.main));
			menuTitleTextView.setTextColor(0xff000000);
			menuTitleTextView.setTypeface(Global.robotoCondensedTypeface);
			menuTitleTextView.setTextSize(MENU_TITLE_FONT_SIZE);
			menuTitleTextView.setLineSpacing(0f, 1.1f);
			menuTitleTextView.setLayoutParams(new ViewGroup.LayoutParams(
					ViewGroup.LayoutParams.MATCH_PARENT,
					ViewGroup.LayoutParams.WRAP_CONTENT
			));
			menuTitleTextView.setVisibility(android.view.View.GONE);

			titleLayout.addView(menuTitleTextView);

			scrollView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT, 1));

			// Keyboard
			String[] buttonResources = {
					"btn_br", "btn_br", "btn_b",
					"btn_br", "btn_br", "btn_b",
					"btn_r", "btn_r", "btn_n"
			};
			if (getString(R.string.screen_type).equals("tablet")) {
				buttonHeight = 85;
			} else if (Global.density < 1.5) {
				buttonHeight = 55;
			}

			keyboardLayout = new MenuLinearLayout(this, true);
			keyboardLayout.setOrientation(LinearLayout.VERTICAL);

			keyboardController = new KeyboardController(this);

			for (int i = 0; i < 3; i++) {
				LinearLayout row = new LinearLayout(this);
				row.setPadding(Helpers.getDp(KeyboardController.PADDING), i == 0 ? Helpers.getDp(KeyboardController.PADDING) : 0, Helpers.getDp(KeyboardController.PADDING), 0);
				row.setOrientation(LinearLayout.HORIZONTAL);
				row.setBackgroundColor(0xc6ffffff);
				for (int j = 0; j < 3; j++) {
					LinearLayout btn = new LinearLayout(this);
					TextView btnText = new TextView(this);
					btnText.setText(String.valueOf(i * 3 + j + 1));
					btnText.setTextColor(0xff000000);
					btnText.setTextSize(17);
					btn.setBackgroundResource(getResources().getIdentifier(buttonResources[i * 3 + j], "drawable", getPackageName()));
					btn.addView(btnText, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
					btn.setGravity(Gravity.CENTER);
					btn.setWeightSum(1);

					row.addView(btn, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, Helpers.getDp(buttonHeight), 1));

					keyboardController.addButton(btn, j, i);
				}

				keyboardLayout.addView(row);
			}

			keyboardLayout.setGravity(Gravity.BOTTOM);
			keyboardLayout.setPadding(0, 0, 0, Helpers.getDp(KeyboardController.PADDING));
			keyboardLayout.setOnTouchListener(keyboardController);
			keyboardLayout.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM));

			hideKeyboardLayout();

			menuBtn = new MenuImageView(this);
			menuBtn.setImageResource(R.drawable.ic_menu);
			menuBtn.setScaleType(ImageView.ScaleType.CENTER);
			menuBtn.setLayoutParams(new FrameLayout.LayoutParams(Helpers.getDp(GAME_MENU_BUTTON_LAYOUT_WIDTH), Helpers.getDp(GAME_MENU_BUTTON_LAYOUT_HEIGHT), Gravity.RIGHT | Gravity.TOP));
			menuBtn.setOnClickListener(new android.view.View.OnClickListener() {
				@Override
				public void onClick(android.view.View v) {
					gameView.showMenu();
				}
			});
			menuBtn.setVisibility(android.view.View.GONE);

			menuLayout = new MenuLinearLayout(this);
			menuLayout.setOrientation(LinearLayout.VERTICAL);
			menuLayout.setLayoutParams(new LinearLayout.LayoutParams(
					LinearLayout.LayoutParams.MATCH_PARENT,
					LinearLayout.LayoutParams.MATCH_PARENT
			));

			portedTextView = new MenuTextView(this);
			portedTextView.setTypeface(Global.robotoCondensedTypeface);
			portedTextView.setTextSize(15);
			portedTextView.setLineSpacing(0f, 1.2f);
			portedTextView.setText(Html.fromHtml(getString(R.string.ported_text)));
			portedTextView.setGravity(Gravity.CENTER);
			portedTextView.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM));
			portedTextView.setPadding(0, 0, 0, Helpers.getDp(10));

			menuLayout.addView(titleLayout);
			menuLayout.addView(scrollView);

			frame.addView(menuLayout);
			frame.addView(keyboardLayout);
			frame.addView(menuBtn);
			frame.addView(portedTextView);

			gameView.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT, 1));
			frame.addView(gameView, 0);

			setContentView(frame);

			gameView._doIV(1); // flag for 1st image, as I understand..
			thread = null;
			m_caseZ = false;
			m_nullI = 2;
			m_forJ = 0;
			m_byteJ = 0;
			inited = false;
			m_ifZ = false;
			wasDestroyed = false;
			restartingStarted = false;

			frame.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
				@Override
				public boolean onPreDraw() {
					frame.getViewTreeObserver().removeOnPreDrawListener(this);
					// setButtonsLayoutHeight();
					doStart();
					return true;
				}
			});



		/* gameView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
			@Override
			public boolean onPreDraw() {
				gameView.getViewTreeObserver().removeOnPreDrawListener(this);
				doStart();
				return true;
			}
		}); */

			/* alive = true;
			m_cZ = false;

			Thread.currentThread().setName("main_thread");

			if (thread == null) {
				thread = new Thread(this);
				thread.setName("game_thread");
			} */

			/*synchronized (thread) {
				thread.start();
				try {
					thread.wait();
				} catch (Throwable e) {
					e.printStackTrace();
				}
			}

			for (int i = 1; i <= 4; i++) {
				menu.load(i);
			}

			wasStarted = true;*/
		}
	}

	protected void doStart() {
		alive = true;
		m_cZ = false;

		Thread.currentThread().setName("main_thread");

		if (thread == null) {
			thread = new Thread(this);
			thread.setName("game_thread");
			thread.start();
		}

		wasStarted = true;
	}

	// protected boolean viewDone = false;

	@Override
	public void run() {
		Helpers.logDebug("!!! run()");
		long l1;

		if (!inited) {
			Helpers.logDebug("run(): initing");
			try {
				// Game view
				/* gameView = new GameView(shared);
				gameView.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT, 1));
				frame.addView(gameView, 0); */

				/* gameView._doIV(1);
				thread = null;
				m_caseZ = false;
				m_nullI = 2;
				m_forJ = 0L;
				seconds = 0L;
				m_byteJ = 0L;
				inited = false;
				m_ifZ = false; */

				long imageDelay = Global.DEBUG ? IMAGES_DELAY_DEBUG : IMAGES_DELAY; // delay of first image
				Thread.yield();

				/*gameView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
					@Override
					public boolean onPreDraw() {
						gameView.getViewTreeObserver().removeOnPreDrawListener(this);
						viewDone = true;
						logDebug("gameView is ready");
						//doStart();
						return true;
					}
				});

				logDebug("before while..");
				while (!viewDone) {
					// Thread.sleep(1);
				}
				logDebug("after while..");*/

				// do we really need this?!
				/*while (gameView == null || gameView.getParent() == null) {
					try {
						Thread.sleep(100);
					} catch (Exception x) {}
				}*/

				MenuHelmetView.clearStaticFields();

				levelsManager = new LevelsManager();
				try {
					levelLoader = new Loader(levelsManager.getCurrentLevelsFile());
				} catch (IOException e) {
					e.printStackTrace();
					// logDebug("Reset level id now");
					levelsManager.resetId();
					levelsManager.reload();

					levelLoader = new Loader(levelsManager.getCurrentLevelsFile());
				}

				physEngine = new Physics(levelLoader);
				gameView.setPhysicsEngine(physEngine);

				// logDebug(levelsManager.getLevelsStat());
				sendStats();

				/* synchronized (Thread.currentThread()) {
					Thread.currentThread().notify();
				} */
				menu = new org.happysanta.gd.Menu.Menu();
				// menu = null;
				// menu.hideKeyboard();
				for (int i = 1; i <= 4; i++) {
					menu.load(i);
				}

				// menu = new Menu();
				// menu.hideKeyboard();

				/*menu.load(1);
				menu.load(2);
				menu.load(3);

				Runnable createMenuRunnable = new Runnable() {
					@Override
					public void run() {
						menu.load(4);
						synchronized (this) {
							notify();
						}
					}
				};

				synchronized (createMenuRunnable) {
					// logDebug("before runOnUiThread()");
					runOnUiThread(createMenuRunnable);
					try {
						// logDebug("before wait()");
						createMenuRunnable.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}*/

				portedTextView.setVisibility(View.VISIBLE);

				gameView.setMenu(menu);
				gameView._doIIV(-50, 150);
				setMode(1);

				// Show first image
				Helpers.logDebug("show first image");
				long l2;
				for (; imageDelay > 0L; imageDelay -= l2)
					l2 = _avJ();

				// Show second image
				portedTextView.setVisibility(View.GONE);
				Helpers.logDebug("show second image");
				imageDelay = Global.DEBUG ? IMAGES_DELAY_DEBUG : IMAGES_DELAY;
				gameView._doIV(2);
				long l3;
				for (long l4 = imageDelay; l4 > 0L; l4 -= l3)
					l3 = _avJ();

				while (m_longI < 10)
					_avJ();

				gameView._doIV(0);
				Helpers.logDebug("images DONE");
				inited = true;

			} catch (Exception _ex) {
				_ex.printStackTrace();
				// Log.w("GDTR", _ex);
				throw new RuntimeException(_ex);
			}
		}

		// logDebug("inited, continue");

		restart(false);
		// logDebug("showMenu() now");

		/*if (menu != null) */
		menu.showMenu(0);
		if (/*menu != null && */menu.canStartTrack())
			restart(true);
		l1 = 0L;

		// try {
		Helpers.logDebug("start main loop");
		while (alive) {
			/*if (!alive) {
				logDebug("!alive");
				break;
			}*/

			// try {
			if (physEngine._bytevI() != menu._jvI()) {
				int j = gameView._intII(menu._jvI());
				physEngine._doIV(j);
				menu._intIV(j);
			}

			if (menuShown) {
				menu.showMenu(1);
				if (menu.canStartTrack())
					restart(true);
			}

			for (int i1 = m_nullI; i1 > 0 && alive; i1--) {
			/* if (m_ifZ)
				seconds += 20L; */
				if (m_forJ == 0L)
					m_forJ = System.currentTimeMillis();
				int k = 0;
				if (/*physEngine != null && */(k = physEngine._dovI()) == 3 && m_byteJ == 0L) {
					m_byteJ = System.currentTimeMillis() + 3000L;
					gameView.showInfoMessage(getString(R.string.crashed), 3000);
					//m_di.postInvalidate();
					//m_di.serviceRepaints();
				}
				if (m_byteJ != 0L && m_byteJ < System.currentTimeMillis())
					restart(true);
				if (k == 5) {
					finishedTime = System.currentTimeMillis();
					gameView.showInfoMessage(getString(R.string.crashed), 3000);
					//m_di.postInvalidate();
					//m_di.serviceRepaints();
					try {
						long l2 = 1000L;
						if (m_byteJ > 0L)
							l2 = Math.min(m_byteJ - System.currentTimeMillis(), 1000L);
						if (l2 > 0L)
							Thread.sleep(l2);
					} catch (InterruptedException _ex) {
					}
					restart(true);
				} else if (k == 4) {
					// logDebug("k == 4");
					m_forJ = 0;
					// seconds = 0;
					startedTime = 0;
					finishedTime = 0;
					pausedTime = 0;
				} else if (k == 1 || k == 2) {
					finishedTime = System.currentTimeMillis();
					// logDebug("game-run: k = " + k);
				/* if (k == 2)
					seconds -= 10L; */
					goalLoop();
					// menu.setLastTrackTime(seconds / 10L);
					menu.setLastTrackTime((finishedTime - startedTime) / 10);
					menu.showMenu(2);

					if (menu.canStartTrack())
						restart(true);
					if (!alive) {
						Helpers.logDebug("!alive (2)");
						break;
					}
				}
				m_ifZ = k != 4;
				if (m_ifZ && startedTime == 0) {
					startedTime = System.currentTimeMillis();
				}
			}

			if (!alive) {
				Helpers.logDebug("!alive (3)");
				break;
			}

			//try {
			/*if (physEngine != null)*/
			physEngine._charvV();
			long l;
			if ((l = System.currentTimeMillis()) - l1 < 30L) {
				try {
					synchronized (this) {
						wait(Math.max(30L - (l - l1), 1L));
					}
				} catch (InterruptedException interruptedexception) {
				}
				l1 = System.currentTimeMillis();
			} else {
				l1 = l;
			}
			//m_di.postInvalidate();
		/*} catch (Exception exception) {
			exception.printStackTrace();
		}*/
		}
		// } catch (Exception e) {
		//	e.printStackTrace();
		//}

		Helpers.logDebug("game thread finished, destroyApp(false) next");

		// finish();
		destroyApp(false);
		// return;
	}

	@Override
	protected void onResume() {
		Helpers.logDebug("@@@ [GDActivity \"+hashCode()+\"] onResume()");
		super.onResume();
		Helpers.logDebug("[GDActivity \"+hashCode()+\"] onResume(), inited = " + inited);
		if (wasPaused && wasStarted) {
			// logDebug("onResume(): wasPaused && wasResumed");
			// start();
			m_cZ = false;
			wasPaused = false;

			// Menu.HelmetRotation.start();
		}
	}

	@Override
	protected void onPause() {
		super.onPause();

		Helpers.logDebug("@@@ [GDActivity " + hashCode() + "] onPause()");

		wasPaused = true;
		m_cZ = true;
		Helpers.logDebug("inited : " + inited);
		if (!menuShown && inited)
			gameToMenu();

		// menu.helmetRotationStop();
		// Menu.HelmetRotation.stop();
		// if (menu != null)
		// 	menu.saveAll();
		// levelsManager.updateLevelSettings();
	}

	@Override
	protected void onDestroy() {
		super.onDestroy();
		Helpers.logDebug("@@@ [GDActivity " + hashCode() + "] onDestroy()");
		destroyApp(false);
	}

	@Override
	protected void onStop() {
		super.onStop();
		Helpers.logDebug("@@@ [GDActivity " + hashCode() + "] onStop()");
	}

	@Override
	protected void onStart() {
		super.onStart();
		Helpers.logDebug("@@@ [GDActivity " + hashCode() + "] onStart()");
	}

	@Override
	protected void onRestart() {
		super.onRestart();
		Helpers.logDebug("@@@ [GDActivity " + hashCode() + "] onRestart()");
	}

	@Override
	public void onBackPressed() {
		if (gameView != null && menu != null && inited) {
			if (menuShown)
				menu.back();
			else
				gameView.showMenu();
		}
	}

	@Override
	public boolean onCreateOptionsMenu(android.view.Menu menu) {
		menu.clear();
		int id = 1;
		for (Command cmd : commands) {
			MenuItem item = menu.add(0, id, 0, cmd.title);
			id++;
		}
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		gameView.commandAction(commands.get(item.getItemId() - 1));
		return true;
	}

	public void setMode(int j) {
		physEngine._byteIV(j);
	}

	public boolean isMenuShown() {
		return menuShown;
	}

	// @UiThread
	public void setMenu(final LinearLayout layout) {
		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				scrollView.removeAllViews();
				if (layout.getParent() != null) {
					((ViewManager) layout.getParent()).removeView(layout);
				}
				scrollView.addView(layout);
			}
		});
	}

	public void goalLoop() {
		if (!alive) {
			return;
		}

		long l1 = 0L;
		if (!physEngine.m_NZ)
			gameView.showInfoMessage(getString(R.string.wheelie), 1000);
		else
			gameView.showInfoMessage(getString(R.string.finished1), 1000);
		for (long l2 = System.currentTimeMillis() + 1000L; l2 > System.currentTimeMillis(); gameView.postInvalidate()) {
			if (menuShown) {
				//m_di.postInvalidate();
				return;
			}
			for (int j = m_nullI; j > 0; j--)
				if (physEngine._dovI() == 5)
					try {
						long l3;
						if ((l3 = l2 - System.currentTimeMillis()) > 0L)
							Thread.sleep(l3);
						return;
					} catch (InterruptedException _ex) {
						return;
					}

			physEngine._charvV();
			long l;
			if ((l = System.currentTimeMillis()) - l1 < 30L) {
				try {
					synchronized (this) {
						wait(Math.max(30L - (l - l1), 1L));
					}
				} catch (InterruptedException interruptedexception) {
				}
				l1 = System.currentTimeMillis();
			} else {
				l1 = l;
			}
		}
	}

	public void restart(boolean flag) {
		// logDebug("[GDActivity] restart()");
		if (!alive) {
			return;
		}

		physEngine._doZV(true);
		// logDebug("[GDActivity] restart(): 1");
		m_forJ = 0;
		// seconds = 0;
		startedTime = 0;
		finishedTime = 0;
		pausedTime = 0;
		m_byteJ = 0;
		if (flag)
			gameView.showInfoMessage(levelLoader.getLevelName(menu.getSelectedLevel(), menu.getSelectedTrack()), 3000);
		// logDebug("[GDActivity] restart(): 2");
		gameView._casevV();
		// logDebug("[GDActivity] restart(): 3");
	}

	public void destroyApp(final boolean restart) {
		if (wasDestroyed) {
			return;
		}

		wasDestroyed = true;
		alive = false;

		final GDActivity self = this;

		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				Helpers.logDebug("[GDActivity " + self.hashCode() + "] destroyApp()");
				inited = false;
				m_caseZ = true;

				synchronized (gameView) {
					destroyResources();

					if (exiting || restart) {
						finish();
					}

					if (restart) {
						doRestartApp();
					}
				}
			}
		});
	}

	private void destroyResources() {
		Helpers.logDebug("[GDActivity " + hashCode() + "]  destroyResources()");

		// if (thread != null) thread.interrupt();
		if (gameView != null) gameView.destroy();

		menuShown = false;
		if (menu != null) {
			if (!fullResetting) menu.saveAll();
			menu.destroy();
		}

		if (levelsManager != null) levelsManager.closeDataSource();
	}

	public int getButtonsLayoutHeight() {
		return buttonHeight * 3 + KeyboardController.PADDING * 2;
	}

	// @UiThread
	public void hideKeyboardLayout() {
		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				keyboardLayout.setVisibility(android.view.View.GONE);

				LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) scrollView.getLayoutParams();
				params.setMargins(0, 0, 0, 0);
				scrollView.setLayoutParams(params);
			}
		});
	}

	// @UiThread
	public void showKeyboardLayout() {
		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				keyboardLayout.setVisibility(android.view.View.VISIBLE);

				LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) scrollView.getLayoutParams();
				params.setMargins(0, 0, 0, Helpers.getDp(getButtonsLayoutHeight()));
				scrollView.setLayoutParams(params);
			}
		});
	}

	public void addCommand(Command cmd) {
		if (!commands.contains(cmd))
			commands.add(cmd);
		if (isNormalAndroid)
			invalidateOptionsMenu();
	}

	public void removeCommand(Command cmd) {
		commands.remove(cmd);
		if (isNormalAndroid)
			invalidateOptionsMenu();
	}

	public void gameToMenu() {
		Helpers.logDebug("gameToMenu()");

		if (gameView == null) {
			Helpers.logDebug("gameToMenu(): gameView == null");
			return;
		}

		pausedTimeStarted = System.currentTimeMillis();

		gameView.removeMenuCommand();
		menuShown = true;
		// menu.helmetRotationStart();
		// Menu.HelmetRotation.start();
		if (menu != null)
			menu.addCommands();

		// hideKeyboardLayout();
		if (!Settings.isKeyboardInMenuEnabled())
			hideKeyboardLayout();
		else
			showKeyboardLayout();

		gameToMenuUpdateUi();
	}

	// @UiThread
	protected void gameToMenuUpdateUi() {
		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				menuBtn.setVisibility(android.view.View.GONE);
				menuTitleTextView.setVisibility(android.view.View.VISIBLE);
				scrollView.setVisibility(android.view.View.VISIBLE);
			}
		});
	}

	public void menuToGame() {
		Helpers.logDebug("menuToGame()");

		if (pausedTimeStarted > 0 && startedTime > 0) {
			pausedTime += (System.currentTimeMillis() - pausedTimeStarted);
			pausedTimeStarted = 0;
		}

		if (menu != null) menu.removeCommands();
		menuShown = false;
		// menu.helmetRotationStop();
		// Menu.HelmetRotation.stop();
		if (gameView != null) gameView.addMenuCommand();
		showKeyboardLayout();
		// menu.showKeyboard();

		menuToGameUpdateUi();

		keyboardController.clearLogBuffer();
	}

	// @UiThread
	protected void menuToGameUpdateUi() {
		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				menuBtn.setVisibility(android.view.View.VISIBLE);
				menuTitleTextView.setVisibility(android.view.View.GONE);
				scrollView.setVisibility(android.view.View.GONE);

				// Clear menu
				scrollView.removeAllViews();
				menuTitleTextView.setText("");
				menu.menuDisabled = true;
				// menu.currentMenu = null;
			}
		});
	}

	public void scrollTextMenuUp() {
		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				int y = scrollView.getScrollY();
				scrollView.scrollTo(0, y - Helpers.getDp(20));
			}
		});
	}

	public void scrollTextMenuDown() {
		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				int y = scrollView.getScrollY();
				scrollView.scrollTo(0, y + Helpers.getDp(20));
			}
		});
	}

	public void scrollToView(final View view) {
		final GDActivity gd = Helpers.getGDActivity();
		final ObservableScrollView scrollView = gd.scrollView;

		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				Rect scrollBounds = new Rect();
				scrollView.getHitRect(scrollBounds);

				if (!view.getLocalVisibleRect(scrollBounds)
						|| scrollBounds.height() < view.getHeight()) {
					int top = view.getTop(),
							height = view.getHeight(),
							scrollY = scrollView.getScrollY(),
							scrollHeight = scrollView.getHeight(),
							y = top;

					/*logDebug("top = " + top);
					logDebug("height = " + height);
					logDebug("scrollY = " + scrollY);
					logDebug("scrollHeight = " + scrollHeight);*/

					if (top < scrollY) {
						// scroll to y
					} else if (top + height > scrollY + scrollHeight) {
						y = top + height - scrollHeight;
						if (y < 0)
							y = 0;
					}

					// logDebug("View is not visible, scroll to y = " + y);
					scrollView.scrollTo(0, y);
				} else {
					// logDebug("View is visible");
				}
			}
		});
	}

	private long _avJ() {
		m_longI++;
		long l = System.currentTimeMillis();
		if (m_longI < 1 || m_longI > 10) { // maybe < 1 not needed?
			m_longI--;
			try {
				Thread.sleep(100L);
			} catch (InterruptedException _ex) {
			}
		}
		return System.currentTimeMillis() - l;
	}

	public void restartApp() {
		if (!restartingStarted) {
			destroyApp(true);
			restartingStarted = true;
		}
	}

	private void doRestartApp() {
		Intent mStartActivity = new Intent(this, GDActivity.class);
		int mPendingIntentId = 123456;
		PendingIntent mPendingIntent = PendingIntent.getActivity(this, mPendingIntentId, mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
		AlarmManager mgr = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
		mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
	}

	private void sendStats() {
		long lastTs = Settings.getLastSendStats();
		Helpers.logDebug("sendStats: lastTs = " + lastTs);
		if (lastTs == 0) {
			Helpers.logDebug("sendStats: set it to current ts and return");
			Settings.setLastSendStats(Helpers.getTimestamp());
			return;
		}

		// if (Helpers.getTimestamp() < lastTs + 3600 * 8) {
		if (Helpers.getTimestamp() < lastTs + 10) {
			Helpers.logDebug("sendStats: just return");
			return;
		}

		final GDActivity self = this;
		Thread statsThread = new Thread() {
			@Override
			public void run() {
				try {
					HashMap<String, Double> stats = levelsManager.getLevelsStat();

					JSONObject statsJSON = new JSONObject(stats);
					String id = Installation.id(self);
					int useCheats = org.happysanta.gd.Menu.Menu.isNameCheat(Settings.getName()) ? 1 : 0;

					API.sendStats(statsJSON.toString(), id, useCheats, new ResponseHandler() {
						@Override
						public void onResponse(Response response) {
							Helpers.logDebug("send stats OK");
							Settings.setLastSendStats(Helpers.getTimestamp());
						}

						@Override
						public void onError(APIException error) {
							Helpers.logDebug("send stats error: " + error.getMessage());
							// logDebug(error);
							// error.printStackTrace();
						}
					});
				} catch (Exception e) {
					Helpers.logDebug("send stats exception: " + e.getMessage());
					// e.printStackTrace();
				}
			}
		};
		statsThread.start();
	}

	public void sendKeyboardLogs() {
		final ProgressDialog progressDialog = ProgressDialog.show(this, getString(R.string.please_wait), getString(R.string.please_wait), true);
		API.sendKeyboardLogs(keyboardController.getLog(), new ResponseHandler() {
			@Override
			public void onResponse(Response response) {
				progressDialog.dismiss();
				keyboardController.clearLogBuffer();
				Helpers.showAlert(getString(R.string.ok), "Done", null);
			}

			@Override
			public void onError(APIException error) {
				progressDialog.dismiss();
				Helpers.showAlert(getString(R.string.error), "Unable to send logs. Maybe log is empty?", null);
			}
		});
	}

	private class ButtonCoords {

		public int x = 0;
		public int y = 0;
		public int w = 0;
		public int h = 0;

		public ButtonCoords() {
		}

		public boolean in(float x, float y) {
			if (x < this.x || x > this.x + this.w || y < this.y || y > this.y + this.h) {
				return false;
			}
			return true;
		}

	}

}
src/org/happysanta/gd/Global.java
public class Global {

	public static final boolean DEBUG = false;
	public static final boolean DISABLE_SCALING = false;
	public static final boolean INSTALLED_FROM_APK = true;
	public static final boolean ACRA_ENABLED = true;
	// public static final boolean ENABLE_TOUCH_HACK_FOR_ALL = false;

	public static float density = 0;
	public static Typeface robotoCondensedTypeface;

	static {
		density = Helpers.getGDActivity().getResources().getDisplayMetrics().density;
		robotoCondensedTypeface = Typeface.createFromAsset(Helpers.getGDActivity().getAssets(), "RobotoCondensed-Regular.ttf");
	}

}
src/org/happysanta/gd/Helpers.java
public static void showConfirm(String title, String message, final Runnable onOk, final Runnable onCancel) {
	Context context = getGDActivity();
	AlertDialog.Builder alert = new AlertDialog.Builder(context)
			.setTitle(title)
			.setMessage(message)
			.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
				@Override
				public void onClick(DialogInterface dialog, int which) {
					if (onOk != null) onOk.run();
				}
			})
			.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
				@Override
				public void onClick(DialogInterface dialog, int which) {
					if (onCancel != null) onCancel.run();
				}
			})
			.setOnCancelListener(new DialogInterface.OnCancelListener() {
				@Override
				public void onCancel(DialogInterface dialog) {
					if (onCancel != null) onCancel.run();
				}
			});
	alert.show();
}
src/org/happysanta/gd/Helpers.java
public static void logDebug(String s) {
	Log.d("AGDTR<" + Thread.currentThread().getName() + ">", s);
}
src/org/happysanta/gd/Menu/Views/MenuLinearLayout.java
public class MenuLinearLayout extends LinearLayout {

	boolean interceptTouchEvents = false;

	public MenuLinearLayout(Context context) {
		super(context);
	}

	public MenuLinearLayout(Context context, boolean interceptTouchEvents) {
		super(context);
		this.interceptTouchEvents = interceptTouchEvents;
	}

	@Override
	public void removeAllViews() {
		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				MenuLinearLayout.super.removeAllViews();
			}
		});
	}

	@Override
	public void setVisibility(final int visibility) {
		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				MenuLinearLayout.super.setVisibility(visibility);
			}
		});
	}

	@Override
	public void addView(final View view) {
		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				MenuLinearLayout.super.addView(view);
			}
		});
	}

	@Override
	public void setPadding(final int left, final int top, final int right, final int bottom) {
		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				MenuLinearLayout.super.setPadding(left, top, right, bottom);
			}
		});
	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent evt) {
		return interceptTouchEvents;
	}

}
src/org/happysanta/gd/Helpers.java
public static GDActivity getGDActivity() {
	return GDActivity.shared;
}
src/org/happysanta/gd/Helpers.java
public static String getString(int r) {
	return GDActivity.shared.getString(r);
}
src/org/happysanta/gd/Menu/Views/MenuImageView.java
public class MenuImageView extends ImageView {

	public MenuImageView(Context context) {
		super(context);
	}

	@Override
	public void setImageResource(final int resid) {
		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				MenuImageView.super.setImageResource(resid);
			}
		});
	}

	@Override
	public void setVisibility(final int visibility) {
		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				MenuImageView.super.setVisibility(visibility);
			}
		});
	}

}
src/org/happysanta/gd/Callback.java
public interface Callback {

	public abstract void onDone(Object... objects);

}
src/org/happysanta/gd/Menu/Views/MenuRelativeLayout.java
public class MenuRelativeLayout extends RelativeLayout {

	public MenuRelativeLayout(Context context) {
		super(context);
	}

	@Override
	public void removeAllViews() {
		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				MenuRelativeLayout.super.removeAllViews();
			}
		});
	}

	@Override
	public void setVisibility(final int visibility) {
		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				MenuRelativeLayout.super.setVisibility(visibility);
			}
		});
	}

	@Override
	public void addView(final View view) {
		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				MenuRelativeLayout.super.addView(view);
			}
		});
	}

	@Override
	public void setPadding(final int left, final int top, final int right, final int bottom) {
		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				MenuRelativeLayout.super.setPadding(left, top, right, bottom);
			}
		});
	}

}
src/org/happysanta/gd/Storage/LevelsManager.java
public class LevelsManager {

	private LevelsDataSource dataSource;
	private boolean dbOK = false;
	private Level currentLevel;

	public LevelsManager() {
		GDActivity gd = getGDActivity();
		dataSource = new LevelsDataSource(gd);

		try {
			dataSource.open();

			if (!dataSource.isDefaultLevelCreated()) {
				Level level = dataSource.createLevel("GDTR original", "Codebrew Software", 10, 10, 10, 0, 0, true, 1);
				logDebug("LevelsManager: Default level created!");
				logDebug(level);
			}
		} catch (SQLException e) {
			e.printStackTrace();
			logDebug("LevelsManager: db feels bad :(");
			// return;
		}

		logDebug("LevelsManager: db feels OK :)");

		// Shared prefs
		// SharedPreferences settings = getSharedPreferences();
		// long levelId = settings.getLong(PREFS_LEVEL_ID, 0);
		long levelId = Settings.getLevelId();
		if (levelId < 1 || !mrgIsAvailable(levelId)) {
			logDebug("LevelsManager: levelId = " + levelId + ", < 1 or mrg is not available; now: reset id");
			/*SharedPreferences.Editor editor = settings.edit();
			editor.putLong(PREFS_LEVEL_ID, 1);
			editor.commit();*/
			resetId();
		}

		reload();
		dbOK = true;
	}

	public void resetId() {
		Settings.setLevelId(1);
	}

	public void reload() {
		long id = Settings.getLevelId();
		currentLevel = dataSource.getLevel(id);

		if (currentLevel == null) {
			logDebug("LevelsManager: failed to load currentLevel; currentId = " + id);
		} else {
			logDebug("LevelsManager: level = " + currentLevel);
		}

		if (Global.ACRA_ENABLED) {
			ACRA.getErrorReporter().putCustomData("level_api_id:", String.valueOf(currentLevel.getApiId()));
		}
	}

	public void closeDataSource() {
		dataSource.close();
	}

	public long getCurrentId() {
		return currentLevel.getId();
	}

	public void setCurrentId(long id) {
		// currentId = id;
		Settings.setLevelId(id);
		/*SharedPreferences settings = getSharedPreferences();
		SharedPreferences.Editor edit = settings.edit();
		edit.putLong(PREFS_LEVEL_ID, id);
		edit.commit();*/
	}

	public Level getCurrentLevel() {
		return currentLevel;
	}

	public File getCurrentLevelsFile() {
		if (currentLevel.getId() > 1)
			return getMrgFileById(currentLevel.getId());

		return null;
	}

	private boolean mrgIsAvailable(long id) {
		if (id == 1) // This is default built-in levels.mrg
			return true;

		File file = getMrgFileById(id);
		return isExternalStorageReadable() && file.exists();
	}

	public boolean isDbOK() {
		return dbOK;
	}

	public long install(File file, String name, String author, long apiId) throws Exception {
		if (!isSpaceAvailable(file.length())) {
			throw new Exception(getString(R.string.e_no_space_left));
		}

		InputStream inputStream = new FileInputStream(file);
		LevelHeader header = Reader.readHeader(inputStream);
		try {
			inputStream.close();
		} catch (IOException e) {
		}

		if (!header.isCountsOk()) {
			throw new IOException(file.getName() + " is not valid");
		}

		Level level = dataSource.createLevel(name, author, header.getCount(0), header.getCount(1), header.getCount(2), 0, getTimestamp(), false, apiId);
		long id = level.getId();
		if (id < 1) {
			throw new Exception(getString(R.string.e_cannot_save_level));
		}

		File newFile = getMrgFileById(id);
		copy(file, newFile);

		return id;
	}

	public void installAsync(File file, String name, String author, long apiId, final DoubleCallback callback) {
		GDActivity gd = getGDActivity();
		final ProgressDialog progressDialog = ProgressDialog.show(gd, getString(R.string.install), getString(R.string.installing), true);

		new AsyncInstallLevel() {
			@Override
			protected void onPostExecute(Object result) {
				progressDialog.dismiss();

				if (result instanceof Throwable) {
					Throwable throwable = (Throwable) result;
					throwable.printStackTrace();
					showAlert(getString(R.string.error), throwable.getMessage(), null);
					if (callback != null)
						callback.onFail();
					return;
				}

				if (callback != null)
					callback.onDone((long) result);
			}
		}.execute(file, name, author, apiId);
	}

	public void load(Level level) throws RuntimeException {
		/*File file = getMrgFileById(level.getId());
		if (!mrgIsAvailable(level.getId())) {
			throw new RuntimeException("Unable to load levels \"" +level.getName() + "\"");
		}*/

		// Loader loader = getLevelLoader();
		// Menu menu = getGameMenu();

		// loader.setLevelsFile(file);
		// menu.reloadLevels();

		setCurrentId(level.getId());
		getGDActivity().restartApp();
	}

	public boolean isApiIdInstalled(long apiId) {
		return dataSource.isApiIdInstalled(apiId);
	}

	public Level[] getInstalledLevels(int offset, int count) {
		return dataSource.getLevels(offset, count).toArray(new Level[0]);
	}

	public Level getLeveL(long id) {
		return dataSource.getLevel(id);
	}

	public Level[] getAllInstalledLevels() {
		return dataSource.getAllLevels().toArray(new Level[0]);
	}

	public synchronized HashMap<String, Double> getLevelsStat() {
		Level[] levels = getAllInstalledLevels();
		HashMap<String, Double> stat = new HashMap<>();
		if (levels.length > 0) {
			for (Level level : levels) {
				int[] completed = level.getUnlockedAll();
				int completedCount = 0;
				for (int i = 0; i < completed.length; i++) {
					if (completed[i] < 0) completed[i] = 0;
					completedCount += completed[i];
				}

				double totalCount = level.getCountEasy() + level.getCountMedium() + level.getCountHard();
				double per = completedCount / totalCount * 100;

				stat.put(String.valueOf(level.getApiId()), per);
			}
		}
		return stat;
	}

	public void delete(Level level) {
		dataSource.deleteLevel(level);
		File file = getMrgFileById(level.getId());
		try {
			if (file.exists()) {
				file.delete();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void deleteAsync(Level level, final Runnable callback) {
		GDActivity gd = getGDActivity();
		final ProgressDialog progressDialog = ProgressDialog.show(gd, getString(R.string.delete), getString(R.string.deleting), true);

		new AsyncDeleteLevel() {
			@Override
			protected void onPostExecute(Void v) {
				progressDialog.dismiss();
				if (callback != null)
					callback.run();
			}
		}.execute(level);
	}

	public void updateLevelSettings() {
		dataSource.updateLevel(currentLevel);
	}

	public void downloadLevel(final Level level, final Callback successCallback) {
		final GDActivity gd = getGDActivity();
		File outputDir = gd.getCacheDir();

		try {
			boolean readable = isExternalStorageReadable();
			if (!readable) {
				throw new Exception(getString(R.string.e_external_storage_is_not_readable));
			}

			if (!isOnline()) {
				throw new Exception(getString(R.string.e_no_network_connection));
			}

			if (!isSpaceAvailable(level.getSize())) {
				throw new Exception(getString(R.string.e_no_space_left));
			}

			final File outputFile = File.createTempFile("levels" + level.getApiId(), "mrg", outputDir);
			FileOutputStream out = new FileOutputStream(outputFile);

			// logDebug("downloadLevel: 4");
			// final API api = new API();
			final ProgressDialog progress;
			final DownloadFile downloadFile = new DownloadFile(API.getMrgURL(level.getApiId()), out);

			progress = new ProgressDialog(gd);
			progress.setMessage(getString(R.string.downloading));
			progress.setIndeterminate(true);
			progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
			progress.setCancelable(true);

			final DownloadHandler handler = new DownloadHandler() {
				@Override
				public void onFinish(Throwable error) {
					progress.dismiss();

					if (error != null) {
						// error.printStackTrace();
						error.printStackTrace();
						showAlert(getString(R.string.error), error.getMessage(), null);

						outputFile.delete();
						return;
					}

					// Install
					installAsync(outputFile, level.getName(), level.getAuthor(), level.getApiId(), new DoubleCallback() {
						@Override
						public void onDone(Object... objects) {
							long id = (long) objects[0];
							outputFile.delete();

							if (successCallback != null)
								successCallback.onDone(id);
						}

						@Override
						public void onFail() {
							outputFile.delete();
						}
					});
				}

				@Override
				public void onStart() {
					progress.show();
				}

				@Override
				public void onProgress(int pr) {
					progress.setIndeterminate(false);
					progress.setMax(100);
					progress.setProgress(pr);
				}
			};
			progress.setOnCancelListener(new DialogInterface.OnCancelListener() {
				@Override
				public void onCancel(DialogInterface dialog) {
					downloadFile.cancel();
					handler.onFinish(new InterruptedException(getString(R.string.e_downloading_was_interrupted)));
				}
			});

			downloadFile.setDownloadHandler(handler);
			downloadFile.start();
		} catch (Exception e) {
			showAlert(getString(R.string.error), e.getMessage(), null);
		}
	}

	public void showSuccessfullyInstalledDialog() {
		GDActivity gd = getGDActivity();
		AlertDialog success = new AlertDialog.Builder(gd)
				.setTitle(getString(R.string.installed))
				.setMessage(getString(R.string.successfully_installed))
				.setPositiveButton(getString(R.string.ok), null)
				.setNegativeButton(getString(R.string.open_installed), new DialogInterface.OnClickListener() {
					@Override
					public void onClick(DialogInterface dialog, int which) {
						Menu menu = getGameMenu();
						MenuScreen currentMenu = getGameMenu().getCurrentMenu(),
								newMenu = menu.managerInstalledScreen;

						if (currentMenu == menu.managerDownloadScreen || currentMenu.getNavTarget() == menu.managerDownloadScreen) {
							menu.managerDownloadScreen.onHide(menu.managerScreen);
						}

						menu.setCurrentMenu(newMenu, false);
					}
				})
				.create();
		success.show();
	}

	public HashMap<Long, Long> findInstalledLevels(ArrayList<Long> apiIds) {
		return dataSource.findInstalledLevels(apiIds);
	}

	public HighScores getHighScores(int level, int track) {
		HighScores scores = dataSource.getHighScores(currentLevel.getId(), level, track);
		// logDebug("LevelsManager.getHighScores: " + scores);
		return scores;
	}

	public void saveHighScores(HighScores scores) {
		dataSource.updateHighScores(scores);
	}

	public void clearHighScores() {
		dataSource.clearHighScores(currentLevel.getId());
	}

	public void clearAllHighScores() {
		dataSource.clearHighScores(0);
	}

	public void resetAllLevelsSettings() {
		dataSource.resetAllLevelsSettings();

		logDebug("All levels now: " + dataSource.getAllLevels());
		logDebug("Level#1: " + dataSource.getLevel(1));
	}

	public static boolean isExternalStorageWritable() {
		String state = Environment.getExternalStorageState();
		if (Environment.MEDIA_MOUNTED.equals(state)) {
			return true;
		}
		return false;
	}

	public static boolean isExternalStorageReadable() {
		String state = Environment.getExternalStorageState();
		if (Environment.MEDIA_MOUNTED.equals(state) ||
				Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
			return true;
		}
		return false;
	}

	public static File getLevelsDirectory() {
		File file = new File(Environment.getExternalStorageDirectory(), "GDLevels");
		if (!file.mkdirs()) {
			logDebug("LevelsManager.getLevelsDirectory: directory not created");
		}
		return file;
	}

	public static String getMrgFileNameById(long id) {
		return getLevelsDirectory().getAbsolutePath() + "/" + id + ".mrg";
	}

	public static File getMrgFileById(long id) {
		if (id == 1) return null;
		return new File(getMrgFileNameById(id));
	}

	public static void copy(File src, File dst) throws IOException {
		InputStream in = new FileInputStream(src);
		OutputStream out = new FileOutputStream(dst);

		byte[] buf = new byte[1024];
		int len;
		while ((len = in.read(buf)) > 0) {
			out.write(buf, 0, len);
		}

		in.close();
		out.close();
	}

	public static boolean isSpaceAvailable(long bytes) {
		StatFs stat = new StatFs(getLevelsDirectory().getPath());
		long bytesAvailable = (long) stat.getBlockSize() * (long) stat.getAvailableBlocks();
		return bytesAvailable >= bytes;
	}

	private class AsyncDeleteLevel extends AsyncTask<Level, Void, Void> {
		@Override
		protected Void doInBackground(Level... levels) {
			delete(levels[0]);
			return null;
		}
	}

	private class AsyncInstallLevel extends AsyncTask<Object, Void, Object> {
		@Override
		protected Object doInBackground(Object... objects) {
			File file = (File) objects[0];
			String name = (String) objects[1];
			String author = (String) objects[2];
			long apiId = (long) objects[3];

			long id = 0;
			try {
				id = install(file, name, author, apiId);
			} catch (Throwable e) {
				return e;
			}

			return id;
		}
	}

}


Question: import android.content.Context;
import android.text.Html;
import android.text.SpannableString;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import org.happysanta.gd.Callback;
import org.happysanta.gd.GDActivity;
import org.happysanta.gd.Global;
import org.happysanta.gd.Menu.Views.LevelNameLeadingMarginSpan2;
import org.happysanta.gd.Menu.Views.MenuImageView;
import org.happysanta.gd.Menu.Views.MenuLinearLayout;
import org.happysanta.gd.Menu.Views.MenuRelativeLayout;
import org.happysanta.gd.Menu.Views.MenuTextView;
import org.happysanta.gd.R;
import org.happysanta.gd.Storage.Level;
import org.happysanta.gd.Storage.LevelsManager;
import static org.happysanta.gd.Helpers.getDp;
import static org.happysanta.gd.Helpers.getGDActivity;
import static org.happysanta.gd.Helpers.getGameMenu;
import static org.happysanta.gd.Helpers.getLevelsManager;
import static org.happysanta.gd.Helpers.getString;
import static org.happysanta.gd.Helpers.logDebug;
import static org.happysanta.gd.Helpers.showConfirm;
		Menu menu = getGameMenu();
		MenuScreen screen = menu.levelScreen;
		LevelsManager levelsManager = getLevelsManager();

		screen.clear();
		// System.gc();

		logDebug(level);

		screen.setNavTarget(this.screen);
		screen.setTitle(level.getName());

		if (!level.getAuthor().equals(""))
			screen.addItem(new BigTextMenuElement(Html.fromHtml(String.format(getString(R.string.author_tpl), level.getAuthor()))));
		if (level.getInstalledTs() > 0)
			screen.addItem(new BigTextMenuElement(Html.fromHtml(String.format(getString(R.string.installed_tpl), level.getFullInstalledDate()))));
		else if (level.getAddedTs() > 0)
			screen.addItem(new BigTextMenuElement(Html.fromHtml(String.format(getString(R.string.added_tpl), level.getFullAddedDate()))));
		screen.addItem(new BigTextMenuElement(Html.fromHtml(String.format(getString(R.string.tracks_tpl), level.getCountEasy() + " / " + level.getCountMedium() + " / " + level.getCountHard()))));
		screen.addItem(menu.createEmptyLine(true));

		if (!level.isInstalled()) {
			ActionMenuElement installAction = menu.createAction(ActionMenuElement.INSTALL);
			installAction.setText(String.format(
					getString(R.string.install_kb), level.getSizeKb()
			));
			installAction.setHandler(this);

			screen.addItem(installAction);
		} else {
			if (!level.isDefault()) {
				ActionMenuElement installed = new ActionMenuElement(getString(R.string.installed), null);
				installed.setDisabled(true);
				screen.addItem(installed);
			}

			if (level.getId() == levelsManager.getCurrentId()) {
				ActionMenuElement active = new ActionMenuElement(getString(R.string.active), null);
				active.setDisabled(true);
				screen.addItem(active);
			} else {
				ActionMenuElement loadAction = menu.createAction(ActionMenuElement.LOAD);
				loadAction.setHandler(this);
				screen.addItem(loadAction);
			}

			if (!level.isDefault() && levelsManager.getCurrentId() != level.getId()) {
				ActionMenuElement deleteAction = menu.createAction(ActionMenuElement.DELETE);
				deleteAction.setHandler(this);

				screen.addItem(deleteAction);
			}
		}

		screen.addItem(menu.createAction(ActionMenuElement.BACK));

		if (menu.getCurrentMenu() != screen) {
			menu.setCurrentMenu(screen, false);
		} else {
			screen.highlightElement();
		}
	}

	@Override
	public MenuScreen getCurrentMenu() {
		return null;
	}

	@Override
	public void setCurrentMenu(MenuScreen e1, boolean flag) {
	}

	@Override
	public void handleAction(MenuElement item) {
		if (item instanceof ActionMenuElement) {
			final GDActivity gd = getGDActivity();
			final Menu menu = getGameMenu();

			switch (((ActionMenuElement) item).getActionValue()) {
				case ActionMenuElement.DELETE:
					if (!level.isInstalled())
						break;

					showConfirm(
							getString(R.string.delete_levels),
							getString(R.string.delete_levels_confirmation),
							new Runnable() {
								@Override
								public void run() {
									gd.levelsManager.deleteAsync(level, new Runnable() {
										@Override
										public void run() {
											long id = level.getId();

											MenuScreen target = menu.getCurrentMenu().getNavTarget();
											if (target instanceof InstalledLevelsMenuScreen) {
												InstalledLevelsMenuScreen installedScreen = (InstalledLevelsMenuScreen) target;
												LevelMenuElement el = installedScreen.getElementByLevelId(id, 0);
												if (el != null)
													installedScreen.deleteElement(el);
												level.setId(0);
												menu.back();
											} else if (target instanceof DownloadLevelsMenuScreen) {
												DownloadLevelsMenuScreen downloadScreen = (DownloadLevelsMenuScreen) target;
												LevelMenuElement el = downloadScreen.getElementByLevelId(id, 0);
												if (el != null)
													el.setInstalled(false);

												level.setId(0);
												buildScreen();
											}
										}
									});
								}
							},
							null
					);
					break;

				case ActionMenuElement.INSTALL:
Answer: 					gd.levelsManager.downloadLevel(level, new Callback() {