/*
   Android Session Activity

   Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz

   This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. 
   If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

package com.freerdp.freerdpcore.presentation;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.inputmethodservice.Keyboard;
import android.inputmethodservice.KeyboardView;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.Toast;
import android.widget.ZoomControls;

import com.freerdp.freerdpcore.R;
import com.freerdp.freerdpcore.application.GlobalApp;
import com.freerdp.freerdpcore.application.GlobalSettings;
import com.freerdp.freerdpcore.application.SessionState;
import com.freerdp.freerdpcore.domain.BookmarkBase;
import com.freerdp.freerdpcore.domain.ConnectionReference;
import com.freerdp.freerdpcore.domain.ManualBookmark;
import com.freerdp.freerdpcore.services.LibFreeRDP;
import com.freerdp.freerdpcore.utils.ClipboardManagerProxy;
import com.freerdp.freerdpcore.utils.KeyboardMapper;
import com.freerdp.freerdpcore.utils.Mouse;

public class SessionActivity extends ActionBarActivity implements
		LibFreeRDP.UIEventListener, KeyboardView.OnKeyboardActionListener,
		ScrollView2D.ScrollView2DListener,
		KeyboardMapper.KeyProcessingListener, SessionView.SessionViewListener,
		TouchPointerView.TouchPointerListener,
		ClipboardManagerProxy.OnClipboardChangedListener {
	private class UIHandler extends Handler {

		public static final int REFRESH_SESSIONVIEW = 1;
		public static final int DISPLAY_TOAST = 2;
		public static final int HIDE_ZOOMCONTROLS = 3;
		public static final int SEND_MOVE_EVENT = 4;
		public static final int SHOW_DIALOG = 5;
		public static final int GRAPHICS_CHANGED = 6;
		public static final int SCROLLING_REQUESTED = 7;

		UIHandler() {
			super();
		}

		@Override
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case GRAPHICS_CHANGED: {
				sessionView.onSurfaceChange(session);
				scrollView.requestLayout();
				break;
			}
			case REFRESH_SESSIONVIEW: {
				sessionView.invalidateRegion();
				break;
			}
			case DISPLAY_TOAST: {
				Toast errorToast = Toast.makeText(getApplicationContext(),
						msg.obj.toString(), Toast.LENGTH_LONG);
				errorToast.show();
				break;
			}
			case HIDE_ZOOMCONTROLS: {
				zoomControls.hide();
				break;
			}
			case SEND_MOVE_EVENT: {
				LibFreeRDP.sendCursorEvent(session.getInstance(), msg.arg1,
						msg.arg2, Mouse.getMoveEvent());
				break;
			}
			case SHOW_DIALOG: {
				// create and show the dialog
				((Dialog) msg.obj).show();
				break;
			}
			case SCROLLING_REQUESTED: {
				int scrollX = 0;
				int scrollY = 0;
				float[] pointerPos = touchPointerView.getPointerPosition();

				if (pointerPos[0] > (screen_width - touchPointerView
						.getPointerWidth()))
					scrollX = SCROLLING_DISTANCE;
				else if (pointerPos[0] < 0)
					scrollX = -SCROLLING_DISTANCE;

				if (pointerPos[1] > (screen_height - touchPointerView
						.getPointerHeight()))
					scrollY = SCROLLING_DISTANCE;
				else if (pointerPos[1] < 0)
					scrollY = -SCROLLING_DISTANCE;

				scrollView.scrollBy(scrollX, scrollY);

				// see if we reached the min/max scroll positions
				if (scrollView.getScrollX() == 0
						|| scrollView.getScrollX() == (sessionView.getWidth() - scrollView
								.getWidth()))
					scrollX = 0;
				if (scrollView.getScrollY() == 0
						|| scrollView.getScrollY() == (sessionView.getHeight() - scrollView
								.getHeight()))
					scrollY = 0;

				if (scrollX != 0 || scrollY != 0)
					uiHandler.sendEmptyMessageDelayed(SCROLLING_REQUESTED,
							SCROLLING_TIMEOUT);
				else
					Log.v(TAG, "Stopping auto-scroll");
				break;
			}
			}
		}
	}

	private class PinchZoomListener extends
			ScaleGestureDetector.SimpleOnScaleGestureListener {
		private float scaleFactor = 1.0f;

		@Override
		public boolean onScaleBegin(ScaleGestureDetector detector) {
			scrollView.setScrollEnabled(false);
			return true;
		}

		@Override
		public boolean onScale(ScaleGestureDetector detector) {

			// calc scale factor
			scaleFactor *= detector.getScaleFactor();
			scaleFactor = Math.max(SessionView.MIN_SCALE_FACTOR,
					Math.min(scaleFactor, SessionView.MAX_SCALE_FACTOR));
			sessionView.setZoom(scaleFactor);

			if (!sessionView.isAtMinZoom() && !sessionView.isAtMaxZoom()) {
				// transform scroll origin to the new zoom space
				float transOriginX = scrollView.getScrollX()
						* detector.getScaleFactor();
				float transOriginY = scrollView.getScrollY()
						* detector.getScaleFactor();

				// transform center point to the zoomed space
				float transCenterX = (scrollView.getScrollX() + detector
						.getFocusX()) * detector.getScaleFactor();
				float transCenterY = (scrollView.getScrollY() + detector
						.getFocusY()) * detector.getScaleFactor();

				// scroll by the difference between the distance of the
				// transformed center/origin point and their old distance
				// (focusX/Y)
				scrollView.scrollBy(
						(int) ((transCenterX - transOriginX) - detector
								.getFocusX()),
						(int) ((transCenterY - transOriginY) - detector
								.getFocusY()));
			}

			return true;
		}

		@Override
		public void onScaleEnd(ScaleGestureDetector de) {
			scrollView.setScrollEnabled(true);
		}
	}

	private class LibFreeRDPBroadcastReceiver extends BroadcastReceiver {
		@Override
		public void onReceive(Context context, Intent intent) {
			// still got a valid session?
			if (session == null)
				return;

			// is this event for the current session?
			if (session.getInstance() != intent.getExtras().getInt(
					GlobalApp.EVENT_PARAM, -1))
				return;

			switch (intent.getExtras().getInt(GlobalApp.EVENT_TYPE, -1)) {
			case GlobalApp.FREERDP_EVENT_CONNECTION_SUCCESS:
				OnConnectionSuccess(context);
				break;

			case GlobalApp.FREERDP_EVENT_CONNECTION_FAILURE:
				OnConnectionFailure(context);
				break;
			case GlobalApp.FREERDP_EVENT_DISCONNECTED:
				OnDisconnected(context);
				break;
			}
		}

		private void OnConnectionSuccess(Context context) {
			Log.v(TAG, "OnConnectionSuccess");

			// bind session
			bindSession();

			if (progressDialog != null) {
				progressDialog.dismiss();
				progressDialog = null;
			}

			// add hostname to history if quick connect was used
			Bundle bundle = getIntent().getExtras();
			if (bundle != null
					&& bundle.containsKey(PARAM_CONNECTION_REFERENCE)) {
				if (ConnectionReference.isHostnameReference(bundle
						.getString(PARAM_CONNECTION_REFERENCE))) {
					assert session.getBookmark().getType() == BookmarkBase.TYPE_MANUAL;
					String item = session.getBookmark().<ManualBookmark> get()
							.getHostname();
					if (!GlobalApp.getQuickConnectHistoryGateway()
							.historyItemExists(item))
						GlobalApp.getQuickConnectHistoryGateway()
								.addHistoryItem(item);
				}
			}
		}

		private void OnConnectionFailure(Context context) {
			Log.v(TAG, "OnConnectionFailure");

			// remove pending move events
			uiHandler.removeMessages(UIHandler.SEND_MOVE_EVENT);

			if (progressDialog != null) {
				progressDialog.dismiss();
				progressDialog = null;
			}

			// post error message on UI thread
			if (!connectCancelledByUser)
				uiHandler.sendMessage(Message.obtain(
						null,
						UIHandler.DISPLAY_TOAST,
						getResources().getText(
								R.string.error_connection_failure)));

			closeSessionActivity(RESULT_CANCELED);
		}

		private void OnDisconnected(Context context) {
			Log.v(TAG, "OnDisconnected");

			// remove pending move events
			uiHandler.removeMessages(UIHandler.SEND_MOVE_EVENT);

			if (progressDialog != null) {
				progressDialog.dismiss();
				progressDialog = null;
			}

			session.setUIEventListener(null);
			closeSessionActivity(RESULT_OK);
		}
	}

	public static final String PARAM_CONNECTION_REFERENCE = "conRef";
	public static final String PARAM_INSTANCE = "instance";

	private static final float ZOOMING_STEP = 0.5f;
	private static final int ZOOMCONTROLS_AUTOHIDE_TIMEOUT = 4000;

	// timeout between subsequent scrolling requests when the touch-pointer is
	// at the edge of the session view
	private static final int SCROLLING_TIMEOUT = 50;
	private static final int SCROLLING_DISTANCE = 20;

	private Bitmap bitmap;
	private SessionState session;
	private SessionView sessionView;
	private TouchPointerView touchPointerView;
	private ProgressDialog progressDialog;
	private KeyboardView keyboardView;
	private KeyboardView modifiersKeyboardView;
	private ZoomControls zoomControls;
	private KeyboardMapper keyboardMapper;

	private Keyboard specialkeysKeyboard;
	private Keyboard numpadKeyboard;
	private Keyboard cursorKeyboard;
	private Keyboard modifiersKeyboard;

	private AlertDialog dlgVerifyCertificate;
	private AlertDialog dlgUserCredentials;
	private View userCredView;

	private UIHandler uiHandler;

	private int screen_width;
	private int screen_height;

	private boolean autoScrollTouchPointer = GlobalSettings
			.getAutoScrollTouchPointer();
	private boolean connectCancelledByUser = false;
	private boolean sessionRunning = false;
	private boolean toggleMouseButtons = false;

	private LibFreeRDPBroadcastReceiver libFreeRDPBroadcastReceiver;

	private static final String TAG = "FreeRDP.SessionActivity";

	private ScrollView2D scrollView;

	// keyboard visibility flags
	private boolean sysKeyboardVisible = false;
	private boolean extKeyboardVisible = false;

	// variables for delayed move event sending
	private static final int MAX_DISCARDED_MOVE_EVENTS = 3;
	private static final int SEND_MOVE_EVENT_TIMEOUT = 150;
	private int discardedMoveEvents = 0;

	private ClipboardManagerProxy mClipboardManager;

	private void createDialogs() {
		// build verify certificate dialog
		dlgVerifyCertificate = new AlertDialog.Builder(this)
				.setTitle(R.string.dlg_title_verify_certificate)
				.setPositiveButton(android.R.string.yes,
						new DialogInterface.OnClickListener() {
							@Override
							public void onClick(DialogInterface dialog,
									int which) {
								callbackDialogResult = true;
								synchronized (dialog) {
									dialog.notify();
								}
							}
						})
				.setNegativeButton(android.R.string.no,
						new DialogInterface.OnClickListener() {
							@Override
							public void onClick(DialogInterface dialog,
									int which) {
								callbackDialogResult = false;
								connectCancelledByUser = true;
								synchronized (dialog) {
									dialog.notify();
								}
							}
						}).setCancelable(false).create();

		// build the dialog
		userCredView = getLayoutInflater().inflate(R.layout.credentials, null,
				true);
		dlgUserCredentials = new AlertDialog.Builder(this)
				.setView(userCredView)
				.setTitle(R.string.dlg_title_credentials)
				.setPositiveButton(android.R.string.ok,
						new DialogInterface.OnClickListener() {
							@Override
							public void onClick(DialogInterface dialog,
									int which) {
								callbackDialogResult = true;
								synchronized (dialog) {
									dialog.notify();
								}
							}
						})
				.setNegativeButton(android.R.string.cancel,
						new DialogInterface.OnClickListener() {
							@Override
							public void onClick(DialogInterface dialog,
									int which) {
								callbackDialogResult = false;
								connectCancelledByUser = true;
								synchronized (dialog) {
									dialog.notify();
								}
							}
						}).setCancelable(false).create();
	}

	private boolean hasHardwareMenuButton() {
		if (Build.VERSION.SDK_INT <= 10)
			return true;
		
		if (Build.VERSION.SDK_INT >= 14) {
			boolean rc = false;
			final ViewConfiguration cfg = ViewConfiguration.get(this);
						
			return cfg.hasPermanentMenuKey();
		}
		
		return false;
	}

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

		// show status bar or make fullscreen?
		if (GlobalSettings.getHideStatusBar()) {
			getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
					WindowManager.LayoutParams.FLAG_FULLSCREEN);
		}

		this.setContentView(R.layout.session);
		if (hasHardwareMenuButton()) {
			this.getSupportActionBar().hide();
		} else
			this.getSupportActionBar().show();

		Log.v(TAG, "Session.onCreate");

		// ATTENTION: We use the onGlobalLayout notification to start our
		// session.
		// This is because only then we can know the exact size of our session
		// when using fit screen
		// accounting for any status bars etc. that Android might throws on us.
		// A bit weird looking
		// but this is the only way ...
		final View activityRootView = findViewById(R.id.session_root_view);
		activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(
				new OnGlobalLayoutListener() {
					@Override
					public void onGlobalLayout() {
						screen_width = activityRootView.getWidth();
						screen_height = activityRootView.getHeight();

						// start session
						if (!sessionRunning && getIntent() != null) {
							processIntent(getIntent());
							sessionRunning = true;
						}
					}
				});

		sessionView = (SessionView) findViewById(R.id.sessionView);
		sessionView.setScaleGestureDetector(new ScaleGestureDetector(this,
				new PinchZoomListener()));
		sessionView.setSessionViewListener(this);
		sessionView.requestFocus();

		touchPointerView = (TouchPointerView) findViewById(R.id.touchPointerView);
		touchPointerView.setTouchPointerListener(this);

		keyboardMapper = new KeyboardMapper();
		keyboardMapper.init(this);
		keyboardMapper.reset(this);

		modifiersKeyboard = new Keyboard(getApplicationContext(),
				R.xml.modifiers_keyboard);
		specialkeysKeyboard = new Keyboard(getApplicationContext(),
				R.xml.specialkeys_keyboard);
		numpadKeyboard = new Keyboard(getApplicationContext(),
				R.xml.numpad_keyboard);
		cursorKeyboard = new Keyboard(getApplicationContext(),
				R.xml.cursor_keyboard);

		// hide keyboard below the sessionView
		keyboardView = (KeyboardView) findViewById(R.id.extended_keyboard);
		keyboardView.setKeyboard(specialkeysKeyboard);
		keyboardView.setOnKeyboardActionListener(this);

		modifiersKeyboardView = (KeyboardView) findViewById(R.id.extended_keyboard_header);
		modifiersKeyboardView.setKeyboard(modifiersKeyboard);
		modifiersKeyboardView.setOnKeyboardActionListener(this);

		scrollView = (ScrollView2D) findViewById(R.id.sessionScrollView);
		scrollView.setScrollViewListener(this);
		uiHandler = new UIHandler();
		libFreeRDPBroadcastReceiver = new LibFreeRDPBroadcastReceiver();

		zoomControls = (ZoomControls) findViewById(R.id.zoomControls);
		zoomControls.hide();
		zoomControls.setOnZoomInClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				resetZoomControlsAutoHideTimeout();
				zoomControls.setIsZoomInEnabled(sessionView
						.zoomIn(ZOOMING_STEP));
				zoomControls.setIsZoomOutEnabled(true);
			}
		});
		zoomControls.setOnZoomOutClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				resetZoomControlsAutoHideTimeout();
				zoomControls.setIsZoomOutEnabled(sessionView
						.zoomOut(ZOOMING_STEP));
				zoomControls.setIsZoomInEnabled(true);
			}
		});

		toggleMouseButtons = false;

		createDialogs();

		// register freerdp events broadcast receiver
		IntentFilter filter = new IntentFilter();
		filter.addAction(GlobalApp.ACTION_EVENT_FREERDP);
		registerReceiver(libFreeRDPBroadcastReceiver, filter);

		mClipboardManager = ClipboardManagerProxy.getClipboardManager(this);
		mClipboardManager.addClipboardChangedListener(this);
	}

	@Override
	protected void onStart() {
		super.onStart();
		Log.v(TAG, "Session.onStart");
	}

	@Override
	protected void onRestart() {
		super.onRestart();
		Log.v(TAG, "Session.onRestart");
	}

	@Override
	protected void onResume() {
		super.onResume();
		Log.v(TAG, "Session.onResume");
	}

	@Override
	protected void onPause() {
		super.onPause();
		Log.v(TAG, "Session.onPause");

		// hide any visible keyboards
		showKeyboard(false, false);
	}

	@Override
	protected void onStop() {
		super.onStop();
		Log.v(TAG, "Session.onStop");
	}

	@Override
	protected void onDestroy() {
		super.onDestroy();
		Log.v(TAG, "Session.onDestroy");

		// Cancel running disconnect timers.
		GlobalApp.cancelDisconnectTimer();

		// Disconnect all remaining sessions.
		Collection<SessionState> sessions = GlobalApp.getSessions();
		for (SessionState session : sessions)
			LibFreeRDP.disconnect(session.getInstance());

		// unregister freerdp events broadcast receiver
		unregisterReceiver(libFreeRDPBroadcastReceiver);

		// remove clipboard listener
		mClipboardManager.removeClipboardboardChangedListener(this);

		// free session
		GlobalApp.freeSession(session.getInstance());
		session = null;
	}

	@Override
	public void onConfigurationChanged(Configuration newConfig) {
		super.onConfigurationChanged(newConfig);

		// reload keyboard resources (changed from landscape)
		modifiersKeyboard = new Keyboard(getApplicationContext(),
				R.xml.modifiers_keyboard);
		specialkeysKeyboard = new Keyboard(getApplicationContext(),
				R.xml.specialkeys_keyboard);
		numpadKeyboard = new Keyboard(getApplicationContext(),
				R.xml.numpad_keyboard);
		cursorKeyboard = new Keyboard(getApplicationContext(),
				R.xml.cursor_keyboard);

		// apply loaded keyboards
		keyboardView.setKeyboard(specialkeysKeyboard);
		modifiersKeyboardView.setKeyboard(modifiersKeyboard);
	}

	private void processIntent(Intent intent) {
		// get either session instance or create one from a bookmark
		Bundle bundle = intent.getExtras();
		if (bundle.containsKey(PARAM_INSTANCE)) {
			int inst = bundle.getInt(PARAM_INSTANCE);
			session = GlobalApp.getSession(inst);
			bitmap = session.getSurface().getBitmap();
			bindSession();
		} else if (bundle.containsKey(PARAM_CONNECTION_REFERENCE)) {
			BookmarkBase bookmark = null;
			String refStr = bundle.getString(PARAM_CONNECTION_REFERENCE);
			if (ConnectionReference.isHostnameReference(refStr)) {
				bookmark = new ManualBookmark();
				bookmark.<ManualBookmark> get().setHostname(
						ConnectionReference.getHostname(refStr));
			} else if (ConnectionReference.isBookmarkReference(refStr)) {
				if (ConnectionReference.isManualBookmarkReference(refStr))
					bookmark = GlobalApp.getManualBookmarkGateway().findById(
							ConnectionReference.getManualBookmarkId(refStr));
				else
					assert false;
			}

			if (bookmark != null)
				connect(bookmark);
			else
				closeSessionActivity(RESULT_CANCELED);
		} else {
			// no session found - exit
			closeSessionActivity(RESULT_CANCELED);
		}
	}

	private void connect(BookmarkBase bookmark) {
		session = GlobalApp.createSession(bookmark);
		session.setUIEventListener(this);

		// set writeable data directory
		LibFreeRDP.setDataDirectory(session.getInstance(), getFilesDir()
				.toString());

		BookmarkBase.ScreenSettings screenSettings = session.getBookmark()
				.getActiveScreenSettings();
		Log.v(TAG, "Screen Resolution: " + screenSettings.getResolutionString());
		if (screenSettings.isAutomatic()) {
			if ((getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE) {
				// large screen device i.e. tablet: simply use screen info
				screenSettings.setHeight(screen_height);
				screenSettings.setWidth(screen_width);
			} else {
				// small screen device i.e. phone:
				// Automatic uses the largest side length of the screen and
				// makes a 16:10 resolution setting out of it
				int screenMax = (screen_width > screen_height) ? screen_width
						: screen_height;
				screenSettings.setHeight(screenMax);
				screenSettings.setWidth((int) ((float) screenMax * 1.6f));
			}
		}
		if (screenSettings.isFitScreen()) {
			screenSettings.setHeight(screen_height);
			screenSettings.setWidth(screen_width);
		}

		progressDialog = new ProgressDialog(this);
		progressDialog.setTitle(bookmark.getLabel());
		progressDialog.setMessage(getResources().getText(
				R.string.dlg_msg_connecting));
		progressDialog.setButton(ProgressDialog.BUTTON_NEGATIVE, "Cancel",
				new DialogInterface.OnClickListener() {
					@Override
					public void onClick(DialogInterface dialog, int which) {
						connectCancelledByUser = true;
						LibFreeRDP.cancelConnection(session.getInstance());
					}
				});
		progressDialog.setCancelable(false);
		progressDialog.show();

		Thread thread = new Thread(new Runnable() {
			public void run() {
				session.connect();
			}
		});
		thread.start();
	}

	// binds the current session to the activity by wiring it up with the
	// sessionView and updating all internal objects accordingly
	private void bindSession() {
		Log.v("SessionActivity", "bindSession called");
		session.setUIEventListener(this);
		sessionView.onSurfaceChange(session);
		scrollView.requestLayout();
		keyboardMapper.reset(this);
	}

	// displays either the system or the extended keyboard or non of them
	private void showKeyboard(boolean showSystemKeyboard,
			boolean showExtendedKeyboard) {
		// no matter what we are doing ... hide the zoom controls
		// TODO: this is not working correctly as hiding the keyboard issues a
		// onScrollChange notification showing the control again ...
		uiHandler.removeMessages(UIHandler.HIDE_ZOOMCONTROLS);
		if (zoomControls.getVisibility() == View.VISIBLE)
			zoomControls.hide();

		InputMethodManager mgr;
		if (showSystemKeyboard) {
			// hide extended keyboard
			keyboardView.setVisibility(View.GONE);

			// show system keyboard
			mgr = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
			if (!mgr.isActive(sessionView))
				Log.e(TAG,
						"Failed to show system keyboard: SessionView is not the active view!");
			mgr.showSoftInput(sessionView, 0);

			// show modifiers keyboard
			modifiersKeyboardView.setVisibility(View.VISIBLE);
		} else if (showExtendedKeyboard) {
			// hide system keyboard
			mgr = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
			mgr.hideSoftInputFromWindow(sessionView.getWindowToken(), 0);

			// show extended keyboard
			keyboardView.setKeyboard(specialkeysKeyboard);
			keyboardView.setVisibility(View.VISIBLE);
			modifiersKeyboardView.setVisibility(View.VISIBLE);
		} else {
			// hide both
			mgr = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
			mgr.hideSoftInputFromWindow(sessionView.getWindowToken(), 0);
			keyboardView.setVisibility(View.GONE);
			modifiersKeyboardView.setVisibility(View.GONE);

			// clear any active key modifiers)
			keyboardMapper.clearlAllModifiers();
		}

		sysKeyboardVisible = showSystemKeyboard;
		extKeyboardVisible = showExtendedKeyboard;
	}

	private void closeSessionActivity(int resultCode) {
		// Go back to home activity (and send intent data back to home)
		setResult(resultCode, getIntent());
		finish();
	}

	// update the state of our modifier keys
	private void updateModifierKeyStates() {
		// check if any key is in the keycodes list

		List<Keyboard.Key> keys = modifiersKeyboard.getKeys();
		for (Iterator<Keyboard.Key> it = keys.iterator(); it.hasNext();) {
			// if the key is a sticky key - just set it to off
			Keyboard.Key curKey = it.next();
			if (curKey.sticky) {
				switch (keyboardMapper.getModifierState(curKey.codes[0])) {
				case KeyboardMapper.KEYSTATE_ON:
					curKey.on = true;
					curKey.pressed = false;
					break;

				case KeyboardMapper.KEYSTATE_OFF:
					curKey.on = false;
					curKey.pressed = false;
					break;

				case KeyboardMapper.KEYSTATE_LOCKED:
					curKey.on = true;
					curKey.pressed = true;
					break;
				}
			}
		}

		// refresh image
		modifiersKeyboardView.invalidateAllKeys();
	}

	private void sendDelayedMoveEvent(int x, int y) {
		if (uiHandler.hasMessages(UIHandler.SEND_MOVE_EVENT)) {
			uiHandler.removeMessages(UIHandler.SEND_MOVE_EVENT);
			discardedMoveEvents++;
		} else
			discardedMoveEvents = 0;

		if (discardedMoveEvents > MAX_DISCARDED_MOVE_EVENTS)
			LibFreeRDP.sendCursorEvent(session.getInstance(), x, y,
					Mouse.getMoveEvent());
		else
			uiHandler.sendMessageDelayed(
					Message.obtain(null, UIHandler.SEND_MOVE_EVENT, x, y),
					SEND_MOVE_EVENT_TIMEOUT);
	}

	private void cancelDelayedMoveEvent() {
		uiHandler.removeMessages(UIHandler.SEND_MOVE_EVENT);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.session_menu, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		// refer to http://tools.android.com/tips/non-constant-fields why we
		// can't use switch/case here ..
		int itemId = item.getItemId();

		if (itemId == R.id.session_touch_pointer) {
			// toggle touch pointer
			if (touchPointerView.getVisibility() == View.VISIBLE) {
				touchPointerView.setVisibility(View.INVISIBLE);
				sessionView.setTouchPointerPadding(0, 0);
			} else {
				touchPointerView.setVisibility(View.VISIBLE);
				sessionView.setTouchPointerPadding(
						touchPointerView.getPointerWidth(),
						touchPointerView.getPointerHeight());
			}
		} else if (itemId == R.id.session_sys_keyboard) {
			showKeyboard(!sysKeyboardVisible, false);
		} else if (itemId == R.id.session_ext_keyboard) {
			showKeyboard(false, !extKeyboardVisible);
		} else if (itemId == R.id.session_disconnect) {
			showKeyboard(false, false);
			LibFreeRDP.disconnect(session.getInstance());
		}

		return true;
	}

	@Override
	public void onBackPressed() {
		// hide keyboards (if any visible) or send alt+f4 to the session
		if (sysKeyboardVisible || extKeyboardVisible)
			showKeyboard(false, false);
		else
			keyboardMapper.sendAltF4();
	}

	// android keyboard input handling
	// We always use the unicode value to process input from the android
	// keyboard except if key modifiers
	// (like Win, Alt, Ctrl) are activated. In this case we will send the
	// virtual key code to allow key
	// combinations (like Win + E to open the explorer).
	@Override
	public boolean onKeyDown(int keycode, KeyEvent event) {
		return keyboardMapper.processAndroidKeyEvent(event);
	}

	@Override
	public boolean onKeyUp(int keycode, KeyEvent event) {
		return keyboardMapper.processAndroidKeyEvent(event);
	}

	// onKeyMultiple is called for input of some special characters like umlauts
	// and some symbol characters
	@Override
	public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
		return keyboardMapper.processAndroidKeyEvent(event);
	}

	// ****************************************************************************
	// KeyboardView.KeyboardActionEventListener
	@Override
	public void onKey(int primaryCode, int[] keyCodes) {
		keyboardMapper.processCustomKeyEvent(primaryCode);
	}

	@Override
	public void onText(CharSequence text) {
	}

	@Override
	public void swipeRight() {
	}

	@Override
	public void swipeLeft() {
	}

	@Override
	public void swipeDown() {
	}

	@Override
	public void swipeUp() {
	}

	@Override
	public void onPress(int primaryCode) {
	}

	@Override
	public void onRelease(int primaryCode) {
	}

	// ****************************************************************************
	// KeyboardMapper.KeyProcessingListener implementation
	@Override
	public void processVirtualKey(int virtualKeyCode, boolean down) {
		LibFreeRDP.sendKeyEvent(session.getInstance(), virtualKeyCode, down);
	}

	@Override
	public void processUnicodeKey(int unicodeKey) {
		LibFreeRDP.sendUnicodeKeyEvent(session.getInstance(), unicodeKey);
	}

	@Override
	public void switchKeyboard(int keyboardType) {
		switch (keyboardType) {
		case KeyboardMapper.KEYBOARD_TYPE_FUNCTIONKEYS:
			keyboardView.setKeyboard(specialkeysKeyboard);
			break;

		case KeyboardMapper.KEYBOARD_TYPE_NUMPAD:
			keyboardView.setKeyboard(numpadKeyboard);
			break;

		case KeyboardMapper.KEYBOARD_TYPE_CURSOR:
			keyboardView.setKeyboard(cursorKeyboard);
			break;

		default:
			break;
		}
	}

	@Override
	public void modifiersChanged() {
		updateModifierKeyStates();
	}

	// ****************************************************************************
	// LibFreeRDP UI event listener implementation
	@Override
	public void OnSettingsChanged(int width, int height, int bpp) {

		if (bpp > 16)
			bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
		else
			bitmap = Bitmap.createBitmap(width, height, Config.RGB_565);

		session.setSurface(new BitmapDrawable(bitmap));

		// check this settings and initial settings - if they are not equal the
		// server doesn't support our settings
		// FIXME: the additional check (settings.getWidth() != width + 1) is for
		// the RDVH bug fix to avoid accidental notifications
		// (refer to android_freerdp.c for more info on this problem)
		BookmarkBase.ScreenSettings settings = session.getBookmark()
				.getActiveScreenSettings();
		if ((settings.getWidth() != width && settings.getWidth() != width + 1)
				|| settings.getHeight() != height
				|| settings.getColors() != bpp)
			uiHandler
					.sendMessage(Message.obtain(
							null,
							UIHandler.DISPLAY_TOAST,
							getResources().getText(
									R.string.info_capabilities_changed)));
	}

	@Override
	public void OnGraphicsUpdate(int x, int y, int width, int height) {
		LibFreeRDP.updateGraphics(session.getInstance(), bitmap, x, y, width,
				height);

		sessionView.addInvalidRegion(new Rect(x, y, x + width, y + height));

		/*
		 * since sessionView can only be modified from the UI thread any
		 * modifications to it need to be scheduled
		 */

		uiHandler.sendEmptyMessage(UIHandler.REFRESH_SESSIONVIEW);
	}

	@Override
	public void OnGraphicsResize(int width, int height, int bpp) {
		// replace bitmap
		if (bpp > 16)
			bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
		else
			bitmap = Bitmap.createBitmap(width, height, Config.RGB_565);
		session.setSurface(new BitmapDrawable(bitmap));

		/*
		 * since sessionView can only be modified from the UI thread any
		 * modifications to it need to be scheduled
		 */
		uiHandler.sendEmptyMessage(UIHandler.GRAPHICS_CHANGED);
	}

	private boolean callbackDialogResult;

	@Override
	public boolean OnAuthenticate(StringBuilder username, StringBuilder domain,
			StringBuilder password) {
		// this is where the return code of our dialog will be stored
		callbackDialogResult = false;

		// set text fields
		((EditText) userCredView.findViewById(R.id.editTextUsername))
				.setText(username);
		((EditText) userCredView.findViewById(R.id.editTextDomain))
				.setText(domain);
		((EditText) userCredView.findViewById(R.id.editTextPassword))
				.setText(password);

		// start dialog in UI thread
		uiHandler.sendMessage(Message.obtain(null, UIHandler.SHOW_DIALOG,
				dlgUserCredentials));

		// wait for result
		try {
			synchronized (dlgUserCredentials) {
				dlgUserCredentials.wait();
			}
		} catch (InterruptedException e) {
		}

		// clear buffers
		username.setLength(0);
		domain.setLength(0);
		password.setLength(0);

		// read back user credentials
		username.append(((EditText) userCredView
				.findViewById(R.id.editTextUsername)).getText().toString());
		domain.append(((EditText) userCredView
				.findViewById(R.id.editTextDomain)).getText().toString());
		password.append(((EditText) userCredView
				.findViewById(R.id.editTextPassword)).getText().toString());

		return callbackDialogResult;
	}

	@Override
	public boolean OnVerifiyCertificate(String subject, String issuer,
			String fingerprint) {

		// see if global settings says accept all
		if (GlobalSettings.getAcceptAllCertificates())
			return true;

		// this is where the return code of our dialog will be stored
		callbackDialogResult = false;

		// set message
		String msg = getResources().getString(
				R.string.dlg_msg_verify_certificate);
		msg = msg + "\n\nSubject: " + subject + "\nIssuer: " + issuer
				+ "\nFingerprint: " + fingerprint;
		dlgVerifyCertificate.setMessage(msg);

		// start dialog in UI thread
		uiHandler.sendMessage(Message.obtain(null, UIHandler.SHOW_DIALOG,
				dlgVerifyCertificate));

		// wait for result
		try {
			synchronized (dlgVerifyCertificate) {
				dlgVerifyCertificate.wait();
			}
		} catch (InterruptedException e) {
		}

		return callbackDialogResult;
	}

	@Override
	public void OnRemoteClipboardChanged(String data) {
		Log.v(TAG, "OnRemoteClipboardChanged: " + data);
		mClipboardManager.setClipboardData(data);
	}

	// ****************************************************************************
	// ScrollView2DListener implementation
	private void resetZoomControlsAutoHideTimeout() {
		uiHandler.removeMessages(UIHandler.HIDE_ZOOMCONTROLS);
		uiHandler.sendEmptyMessageDelayed(UIHandler.HIDE_ZOOMCONTROLS,
				ZOOMCONTROLS_AUTOHIDE_TIMEOUT);
	}

	@Override
	public void onScrollChanged(ScrollView2D scrollView, int x, int y,
			int oldx, int oldy) {
		zoomControls.setIsZoomInEnabled(!sessionView.isAtMaxZoom());
		zoomControls.setIsZoomOutEnabled(!sessionView.isAtMinZoom());
		if (!GlobalSettings.getHideZoomControls()
				&& zoomControls.getVisibility() != View.VISIBLE)
			zoomControls.show();
		resetZoomControlsAutoHideTimeout();
	}

	// ****************************************************************************
	// SessionView.SessionViewListener
	@Override
	public void onSessionViewBeginTouch() {
		scrollView.setScrollEnabled(false);
	}

	@Override
	public void onSessionViewEndTouch() {
		scrollView.setScrollEnabled(true);
	}

	@Override
	public void onSessionViewLeftTouch(int x, int y, boolean down) {
		if (!down)
			cancelDelayedMoveEvent();

		LibFreeRDP.sendCursorEvent(
				session.getInstance(),
				x,
				y,
				toggleMouseButtons ? Mouse.getRightButtonEvent(down) : Mouse
						.getLeftButtonEvent(down));

		if (!down)
			toggleMouseButtons = false;
	}

	public void onSessionViewRightTouch(int x, int y, boolean down) {
		if (!down)
			toggleMouseButtons = !toggleMouseButtons;
	}

	@Override
	public void onSessionViewMove(int x, int y) {
		sendDelayedMoveEvent(x, y);
	}

	@Override
	public void onSessionViewScroll(boolean down) {
		LibFreeRDP.sendCursorEvent(session.getInstance(), 0, 0,
				Mouse.getScrollEvent(down));
	}

	// ****************************************************************************
	// TouchPointerView.TouchPointerListener
	@Override
	public void onTouchPointerClose() {
		touchPointerView.setVisibility(View.INVISIBLE);
		sessionView.setTouchPointerPadding(0, 0);
	}

	private Point mapScreenCoordToSessionCoord(int x, int y) {
		int mappedX = (int) ((float) (x + scrollView.getScrollX()) / sessionView
				.getZoom());
		int mappedY = (int) ((float) (y + scrollView.getScrollY()) / sessionView
				.getZoom());
		if (mappedX > bitmap.getWidth())
			mappedX = bitmap.getWidth();
		if (mappedY > bitmap.getHeight())
			mappedY = bitmap.getHeight();
		return new Point(mappedX, mappedY);
	}

	@Override
	public void onTouchPointerLeftClick(int x, int y, boolean down) {
		Point p = mapScreenCoordToSessionCoord(x, y);
		LibFreeRDP.sendCursorEvent(session.getInstance(), p.x, p.y,
				Mouse.getLeftButtonEvent(down));
	}

	@Override
	public void onTouchPointerRightClick(int x, int y, boolean down) {
		Point p = mapScreenCoordToSessionCoord(x, y);
		LibFreeRDP.sendCursorEvent(session.getInstance(), p.x, p.y,
				Mouse.getRightButtonEvent(down));
	}

	@Override
	public void onTouchPointerMove(int x, int y) {
		Point p = mapScreenCoordToSessionCoord(x, y);
		LibFreeRDP.sendCursorEvent(session.getInstance(), p.x, p.y,
				Mouse.getMoveEvent());

		if (autoScrollTouchPointer
				&& !uiHandler.hasMessages(UIHandler.SCROLLING_REQUESTED)) {
			Log.v(TAG, "Starting auto-scroll");
			uiHandler.sendEmptyMessageDelayed(UIHandler.SCROLLING_REQUESTED,
					SCROLLING_TIMEOUT);
		}
	}

	@Override
	public void onTouchPointerScroll(boolean down) {
		LibFreeRDP.sendCursorEvent(session.getInstance(), 0, 0,
				Mouse.getScrollEvent(down));
	}

	@Override
	public void onTouchPointerToggleKeyboard() {
		showKeyboard(!sysKeyboardVisible, false);
	}

	@Override
	public void onTouchPointerToggleExtKeyboard() {
		showKeyboard(false, !extKeyboardVisible);
	}

	@Override
	public void onTouchPointerResetScrollZoom() {
		sessionView.setZoom(1.0f);
		scrollView.scrollTo(0, 0);
	}

	// ****************************************************************************
	// ClipboardManagerProxy.OnClipboardChangedListener
	@Override
	public void onClipboardChanged(String data) {
		Log.v(TAG, "onClipboardChanged: " + data);
		LibFreeRDP.sendClipboardData(session.getInstance(), data);
	}
}
